diff --git a/README.md b/README.md index 73d993914..7e6d8ef5d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Inform 7 -v10.1.0-beta+6V34 'Krypton' (24 July 2022) +v10.1.0-beta+6V35 'Krypton' (26 July 2022) ## About Inform 7 diff --git a/build.txt b/build.txt index b9ee9b661..b0adee575 100644 --- a/build.txt +++ b/build.txt @@ -1,3 +1,3 @@ Prerelease: beta -Build Date: 24 July 2022 -Build Number: 6V34 +Build Date: 26 July 2022 +Build Number: 6V35 diff --git a/docs/BasicInformKit/S-zmc.html b/docs/BasicInformKit/S-zmc.html index 8dd20b92b..1cff893bb 100644 --- a/docs/BasicInformKit/S-zmc.html +++ b/docs/BasicInformKit/S-zmc.html @@ -239,8 +239,8 @@ convert between record numbers and dictionary addresses. rfalse; ]; -[ VM_DictionaryAddressToNumber w; return (w-(HDR_DICTIONARY-->0 + 7))/9; ]; -[ VM_NumberToDictionaryAddress n; return HDR_DICTIONARY-->0 + 7 + 9*n; ]; +[ VM_DictionaryAddressToNumber w; return (w-(HDR_DICTIONARY-->0 + 7))/DICT_ENTRY_BYTES; ]; +[ VM_NumberToDictionaryAddress n; return HDR_DICTIONARY-->0 + 7 + DICT_ENTRY_BYTES*n; ];

§8. Command Tables. The VM is also generated containing a data structure for the grammar produced by I6's Verb and Extend directives: this is essentially a @@ -257,7 +257,7 @@ recognised by the parser. [ VM_PrintCommandWords i da j; da = HDR_DICTIONARY-->0; for (j=0 : j<(da+5)-->0 : j++) - if (da->(j*9 + 14) == $ff-i) + if (da->(j*DICT_ENTRY_BYTES + 14) == $ff-i) print "'", (address) VM_NumberToDictionaryAddress(j), "' "; ]; diff --git a/docs/final-module/4-fi6.html b/docs/final-module/4-fi6.html index 89c0a78c7..8384f80b7 100644 --- a/docs/final-module/4-fi6.html +++ b/docs/final-module/4-fi6.html @@ -181,6 +181,10 @@ we will need for the code we are compiling. But this seems a good time to make i WRITE("Global debug_flag;\n"); WRITE("Global or_tmp_var;\n"); CodeGen::deselect(gen, saved); + saved = CodeGen::select(gen, ICL_directives_I7CGS); + OUT = CodeGen::current(gen); + WRITE("!%% $ZCODE_LESS_DICT_DATA=1;\n"); + CodeGen::deselect(gen, saved);

§4.2. As noted above, I6 will add a veneer of code to what we compile. That veneer diff --git a/docs/final-module/4-i6c.html b/docs/final-module/4-i6c.html index e5777c20c..ed758618e 100644 --- a/docs/final-module/4-i6c.html +++ b/docs/final-module/4-i6c.html @@ -164,6 +164,7 @@ for example. (Str::eq(const_name, I"TARGET_ZCODE")) || (Str::eq(const_name, I"TARGET_GLULX")) || (Str::eq(const_name, I"DICT_WORD_SIZE")) || + (Str::eq(const_name, I"DICT_ENTRY_BYTES")) || (Str::eq(const_name, I"DEBUG")) || (Str::eq(const_name, I"cap_short_name"))) { ifndef_me = TRUE; diff --git a/docs/html-module/2-if.html b/docs/html-module/2-if.html index 71c7d09f9..604415e4b 100644 --- a/docs/html-module/2-if.html +++ b/docs/html-module/2-if.html @@ -79,7 +79,8 @@ but they're just plain old files, and are not managed by Inbuild as "copies". enum CSS_SET_BY_PLATFORM_IRES enum CSS_FOR_STANDARD_PAGES_IRES enum EXTENSION_DOCUMENTATION_MODEL_IRES -enum JSON_REQUIREMENTS_IRES +enum RESOURCE_JSON_REQS_IRES +enum REGISTRY_JSON_REQS_IRES

 filename *InstalledFiles::filename(int ires) {
@@ -97,8 +98,10 @@ but they're just plain old files, and are not managed by Inbuild as "copies".
                 return Filenames::in(misc, I"DefaultCover.jpg");
         case SMALL_DEFAULT_COVER_ART_IRES:
                 return Filenames::in(misc, I"Small Cover.jpg");
-        case JSON_REQUIREMENTS_IRES:
-                return Filenames::in(misc, I"metadata.jsonr");
+        case RESOURCE_JSON_REQS_IRES:
+                return Filenames::in(misc, I"resource.jsonr");
+        case REGISTRY_JSON_REQS_IRES:
+                return Filenames::in(misc, I"registry.jsonr");
 
         case CBLORB_REPORT_MODEL_IRES:
                 return InstalledFiles::varied_by_platform(models, I"CblorbModel.html");
diff --git a/docs/inbuild/1-mn.html b/docs/inbuild/1-mn.html
index 78dd14dcf..84b0d74b0 100644
--- a/docs/inbuild/1-mn.html
+++ b/docs/inbuild/1-mn.html
@@ -71,6 +71,7 @@ function togglePopup(material_id) {
 pathname *path_to_tools = NULL;
 int dry_run_mode = FALSE, build_trace_mode = FALSE;
 inbuild_nest *destination_nest = NULL;
+inbuild_registry *selected_registry = NULL;
 text_stream *filter_text = NULL;
 

§2. Main routine. When Inbuild is called at the command line, it begins at main, like all C @@ -395,6 +396,8 @@ other options to the selection defined here. enum COPY_TO_CLSW enum SYNC_TO_CLSW enum VERSIONS_IN_FILENAMES_CLSW +enum VERIFY_REGISTRY_CLSW +enum BUILD_REGISTRY_CLSW

Read the command line2.8 =

@@ -443,6 +446,10 @@ other options to the selection defined here. L"apply to all works in nest(s) matching requirement X"); CommandLine::declare_switch(CONTENTS_OF_CLSW, L"contents-of", 2, L"apply to all targets in the directory X"); + CommandLine::declare_switch(VERIFY_REGISTRY_CLSW, L"verify-registry", 2, + L"verify roster.json metadata of registry in the directory X"); + CommandLine::declare_switch(BUILD_REGISTRY_CLSW, L"build-registry", 2, + L"construct HTML menu pages for registry in the directory X"); Supervisor::declare_options(); CommandLine::read(argc, argv, NULL, &Main::option, &Main::bareword); @@ -485,6 +492,13 @@ other options to the selection defined here. break; case VERSIONS_IN_FILENAMES_CLSW: Editions::set_canonical_leaves_have_versions(val); break; + case VERIFY_REGISTRY_CLSW: + case BUILD_REGISTRY_CLSW: + selected_registry = Registries::new(Pathnames::from_text(arg)); + if (Registries::read_roster(selected_registry) == FALSE) exit(1); + if (id == BUILD_REGISTRY_CLSW) + Registries::build(selected_registry); + break; } Supervisor::option(id, val, arg, state); } diff --git a/docs/inbuild/M-agtk.html b/docs/inbuild/M-agtk.html index f65448fd1..be73aed57 100644 --- a/docs/inbuild/M-agtk.html +++ b/docs/inbuild/M-agtk.html @@ -631,7 +631,7 @@ In the mean time, it seems premature to commit to any model.

diff --git a/docs/inbuild/M-agtlb.html b/docs/inbuild/M-agtlb.html index f4d879958..4089e92c5 100644 --- a/docs/inbuild/M-agtlb.html +++ b/docs/inbuild/M-agtlb.html @@ -253,7 +253,7 @@ Note that this takes effect only if the user asks it to with a use option: Use French language index. diff --git a/docs/inbuild/M-agtpm.html b/docs/inbuild/M-agtpm.html index 2b63e84e8..ef3c8efa1 100644 --- a/docs/inbuild/M-agtpm.html +++ b/docs/inbuild/M-agtpm.html @@ -166,7 +166,7 @@ use of -basic t

diff --git a/docs/inbuild/M-agtr.html b/docs/inbuild/M-agtr.html new file mode 100644 index 000000000..b74a67964 --- /dev/null +++ b/docs/inbuild/M-agtr.html @@ -0,0 +1,76 @@ + + + + A Guide to Registries + + + + + + + + + + + + + + + +
+ + +

Provisional documentation on how to scan and build registries of Inform resources.

+ +
+ +

§1. Registries. A "registry" is a collection of Inform resources gathered together for others +to use. The Public Library presented in the Inform apps is a registry, for +example, but the idea is being generalised in inbuild with a view to future +developments. +

+ +

This page is a place-holder for now: see Registries (in supervisor) for +very slightly more. +

+ +

The PL presented as a registry can be seen at this repository. +

+ + + +
+ + + diff --git a/docs/inbuild/M-rc.html b/docs/inbuild/M-rc.html index 5379bc085..898a749a4 100644 --- a/docs/inbuild/M-rc.html +++ b/docs/inbuild/M-rc.html @@ -67,6 +67,7 @@ and those not documented in this manual are covered in that one. -build-locate show file paths of all the extensions, kits and so on needed to build -build-missing show the extensions, kits and so on which are needed to build but missing -build-needs show all the extensions, kits and so on needed to build +-build-registry X construct HTML menu pages for registry in the directory X -build-trace show verbose reasoning during -build (default is -no-build-trace) -contents-of X apply to all targets in the directory X -copy-to X copy target(s) to nest X @@ -80,6 +81,7 @@ and those not documented in this manual are covered in that one. -use-locate show file paths of all the extensions, kits and so on needed to use -use-missing show the extensions, kits and so on which are needed to use but missing -use-needs show all the extensions, kits and so on needed to use +-verify-registry X verify roster.json metadata of registry in the directory X -no-versions-in-filenames don't append _v number to destination filenames on -copy-to or -sync-to (default is -versions-in-filenames) for translating Inform source text to Inter: @@ -115,7 +117,7 @@ and those not documented in this manual are covered in that one. -version print out version number diff --git a/docs/inbuild/M-ui.html b/docs/inbuild/M-ui.html index c8dc3c5bd..bdf5cbf85 100644 --- a/docs/inbuild/M-ui.html +++ b/docs/inbuild/M-ui.html @@ -627,7 +627,7 @@ the project will still work exactly as it originally did.

diff --git a/docs/inbuild/index.html b/docs/inbuild/index.html index 9ab76800e..e89eba9df 100644 --- a/docs/inbuild/index.html +++ b/docs/inbuild/index.html @@ -73,6 +73,11 @@ A Guide to Project Metadata - Provisional documentation on giving Inform projects JSON-based metadata.

+
  • +

    + A Guide to Registries - + Provisional documentation on how to scan and build registries of Inform resources.

    +
  • Reference Card - diff --git a/docs/runtime-module/2-hrr.html b/docs/runtime-module/2-hrr.html index 9ff7881d5..1c476f1e0 100644 --- a/docs/runtime-module/2-hrr.html +++ b/docs/runtime-module/2-hrr.html @@ -846,6 +846,7 @@ and The Standard Kits ( enum SLASH_TOKENS_HAP enum SLASH_FN_HL enum REPARSE_CODE_HL +enum DICT_ENTRY_BYTES_HL enum DICT_WORD_SIZE_HL enum VERB_DIRECTIVE_META_HL enum VERB_DIRECTIVE_NOUN_FILTER_HL @@ -902,6 +903,7 @@ and The Standard Kits ( H_BEGIN(LocationRequirements::generic_submodule(I, grammar)) H_C_T(REPARSE_CODE_HL, I"REPARSE_CODE") + H_C_T(DICT_ENTRY_BYTES_HL, I"DICT_ENTRY_BYTES") H_C_T(DICT_WORD_SIZE_HL, I"DICT_WORD_SIZE") H_C_T(VERB_DIRECTIVE_META_HL, I"VERB_DIRECTIVE_META") H_C_T(VERB_DIRECTIVE_NOUN_FILTER_HL, I"VERB_DIRECTIVE_NOUN_FILTER") diff --git a/docs/runtime-module/7-cg.html b/docs/runtime-module/7-cg.html index 8f88bb3eb..17bc945f8 100644 --- a/docs/runtime-module/7-cg.html +++ b/docs/runtime-module/7-cg.html @@ -106,6 +106,8 @@ fact rewritten it, so that the whole command must be re-parsed afresh. if (N <= 0) N = 9; } RTCommandGrammars::grammar_constant(DICT_WORD_SIZE_HL, N); + if (TargetVMs::is_16_bit(VM)) + RTCommandGrammars::grammar_constant(DICT_ENTRY_BYTES_HL, 8); }

    §3.

    diff --git a/docs/supervisor-module/1-sm.html b/docs/supervisor-module/1-sm.html index 18386b832..089a7552f 100644 --- a/docs/supervisor-module/1-sm.html +++ b/docs/supervisor-module/1-sm.html @@ -86,6 +86,7 @@ which use this module: enum inbuild_edition_CLASS enum inbuild_genre_CLASS enum inbuild_nest_CLASS +enum inbuild_registry_CLASS enum inbuild_requirement_CLASS enum inbuild_search_result_CLASS enum inbuild_work_CLASS @@ -117,6 +118,7 @@ which use this module: DECLARE_CLASS(inbuild_edition) DECLARE_CLASS(inbuild_genre) DECLARE_CLASS(inbuild_nest) +DECLARE_CLASS(inbuild_registry) DECLARE_CLASS(inbuild_requirement) DECLARE_CLASS(inbuild_search_result) DECLARE_CLASS(inbuild_work) diff --git a/docs/supervisor-module/2-ce.html b/docs/supervisor-module/2-ce.html index 39d04dade..cb88ad326 100644 --- a/docs/supervisor-module/2-ce.html +++ b/docs/supervisor-module/2-ce.html @@ -296,7 +296,7 @@ output. diff --git a/docs/supervisor-module/2-cps.html b/docs/supervisor-module/2-cps.html index bf4ac97ac..0a7f0c15f 100644 --- a/docs/supervisor-module/2-cps.html +++ b/docs/supervisor-module/2-cps.html @@ -331,7 +331,7 @@ its main task: building an Inform project. } diff --git a/docs/supervisor-module/2-edt.html b/docs/supervisor-module/2-edt.html index a920ee48b..6ab96bceb 100644 --- a/docs/supervisor-module/2-edt.html +++ b/docs/supervisor-module/2-edt.html @@ -148,7 +148,7 @@ contributes only the un-filename-extended leafname } diff --git a/docs/supervisor-module/2-gnr.html b/docs/supervisor-module/2-gnr.html index f3611d224..1a7aa94ec 100644 --- a/docs/supervisor-module/2-gnr.html +++ b/docs/supervisor-module/2-gnr.html @@ -242,7 +242,7 @@ the Inbuild command-line options build_methodology *meth) diff --git a/docs/supervisor-module/2-jm.html b/docs/supervisor-module/2-jm.html index 9917252a2..1f3356fc3 100644 --- a/docs/supervisor-module/2-jm.html +++ b/docs/supervisor-module/2-jm.html @@ -91,7 +91,7 @@ identifying the copy which it purports to identify. if (compatibility) Extract compatibility1.6; } -void JSONMetadata::read_metadata_file_helper(text_stream *text, text_file_position *tfp, +void JSONMetadata::read_metadata_file_helper(text_stream *text, text_file_position *tfp, void *v_state) { text_stream *contents = (text_stream *) v_state; WRITE_TO(contents, "%S\n", text); @@ -289,7 +289,7 @@ checking here results in more explicit error messages.

    -void JSONMetadata::not_both(inbuild_copy *C, JSON_value *clause, text_stream *where) {
    +void JSONMetadata::not_both(inbuild_copy *C, JSON_value *clause, text_stream *where) {
         if (clause) {
             JSON_value *version = JSON::look_up_object(clause, I"version");
             JSON_value *version_range = JSON::look_up_object(clause, I"version-range");
    @@ -339,9 +339,9 @@ loads only once.
     
     dictionary *JSON_resource_metadata_requirements = NULL;
     
    -JSON_requirement *JSONMetadata::requirements(void) {
    +JSON_requirement *JSONMetadata::requirements(void) {
         if (JSON_resource_metadata_requirements == NULL) {
    -        filename *F = InstalledFiles::filename(JSON_REQUIREMENTS_IRES);
    +        filename *F = InstalledFiles::filename(RESOURCE_JSON_REQS_IRES);
             JSON_resource_metadata_requirements = JSON::read_requirements_file(NULL, F);
         }
         JSON_requirement *req =
    @@ -351,7 +351,7 @@ loads only once.
     }
     
    diff --git a/docs/supervisor-module/2-nst.html b/docs/supervisor-module/2-nst.html index 3a4fab105..f8afe595d 100644 --- a/docs/supervisor-module/2-nst.html +++ b/docs/supervisor-module/2-nst.html @@ -73,7 +73,7 @@ positions in the file system hierarchy which may or may not exist. int tag_value; used to indicate whether internal, external, and such CLASS_DEFINITION } inbuild_nest; -inbuild_nest *Nests::new(pathname *P) { +inbuild_nest *Nests::new(pathname *P) { inbuild_nest *N = CREATE(inbuild_nest); N->location = P; N->read_only = FALSE; @@ -81,7 +81,7 @@ positions in the file system hierarchy which may or may not exist. return N; }
    -
    • The structure inbuild_nest is accessed in 1/ic, 3/bg, 3/is2, 4/em, 4/km, 4/lm, 4/pm, 4/tm, 5/ps2 and here.
    +
    • The structure inbuild_nest is accessed in 1/ic, 2/rgs, 3/bg, 3/is2, 4/em, 4/km, 4/lm, 4/pm, 4/tm, 5/ps2 and here.

    §2. Nests used by the Inform and Inbuild tools are tagged with the following constants. (There used to be quite a good joke here, but refactoring of the code removed its premise. Literate programming is like that sometimes.) @@ -151,7 +151,7 @@ we create one of these for each hit: CLASS_DEFINITION } inbuild_search_result; -

    • The structure inbuild_search_result is accessed in 2/ce, 3/is, 5/ks, 5/ls, 6/inc, 7/dct, 7/cns, 7/ip, 7/ip2 and here.
    +
    • The structure inbuild_search_result is accessed in 2/ce, 2/rgs, 3/is, 5/ks, 5/ls, 6/inc, 7/dct, 7/cns, 7/ip, 7/ip2 and here.

    §6. These can be created only as entries in a list:

    @@ -226,7 +226,7 @@ semantic version numbers. } diff --git a/docs/supervisor-module/2-rgs.html b/docs/supervisor-module/2-rgs.html new file mode 100644 index 000000000..909b423a0 --- /dev/null +++ b/docs/supervisor-module/2-rgs.html @@ -0,0 +1,743 @@ + + + + Registries + + + + + + + + + + + + + + + + + + +
    + + +

    Registries are nests provided with metadata and intended to be presented as an online source from which Inform resources can be downloaded.

    + +
    + +

    §1. Creation. To "create" a registry here does not mean actually altering the file system, for +example by making a directory: registries here are merely notes in memory of +positions in the file system hierarchy which may or may not exist. +

    + +
    +typedef struct inbuild_registry {
    +    struct pathname *location;
    +    struct inbuild_nest *nest;
    +    struct JSON_value *roster;
    +    CLASS_DEFINITION
    +} inbuild_registry;
    +
    +
    • The structure inbuild_registry is accessed in 1/ic, 2/nst, 3/bg, 3/is2, 4/em, 4/km, 4/lm, 4/pm, 4/tm, 5/ps2, 6/inc, 7/cns, 7/ip and here.
    +

    §2.

    + +
    +inbuild_registry *Registries::new(pathname *P) {
    +    inbuild_registry *N = CREATE(inbuild_registry);
    +    N->location = P;
    +    N->nest = Nests::new(Pathnames::down(P, I"payloads"));
    +    N->roster = NULL;
    +    return N;
    +}
    +
    +

    §3. The roster. This is a JSON file called roster.json, whose schema must match the one specified +by the file registry-metadata.jsonr in the standard Inform distribution. +

    + +

    The following silently returns TRUE if it does, or prints errors and returns +FALSE if not (or if it doesn't exist). +

    + +
    +int Registries::read_roster(inbuild_registry *R) {
    +    if (R == NULL) internal_error("no registry");
    +    R->roster = NULL;
    +    filename *F = Filenames::in(R->location, I"roster.json");
    +    if (TextFiles::exists(F) == FALSE) {
    +        WRITE_TO(STDERR, "%f: roster file does not exist\n", F);
    +        return FALSE;
    +    }
    +    TEMPORARY_TEXT(contents)
    +    TextFiles::read(F, FALSE, "unable to read file of JSON metadata", TRUE,
    +        &JSONMetadata::read_metadata_file_helper, NULL, contents);
    +    text_file_position tfp = TextFiles::at(F, 1);
    +    JSON_value *obj = JSON::decode(contents, &tfp);
    +    DISCARD_TEXT(contents)
    +    if ((obj) && (obj->JSON_type == ERROR_JSONTYPE)) {
    +        WRITE_TO(STDERR, "%f: JSON syntax error: %S\n", F, obj->if_error);
    +        return FALSE;
    +    } else {
    +        JSON_requirement *req = Registries::requirements();
    +        linked_list *validation_errors = NEW_LINKED_LIST(text_stream);
    +        if (JSON::validate(obj, req, validation_errors) == FALSE) {
    +            text_stream *err;
    +            LOOP_OVER_LINKED_LIST(err, text_stream, validation_errors) {
    +                WRITE_TO(STDERR, "%f: metadata did not validate: '%S'\n", F, err);
    +            }
    +            return FALSE;
    +        }
    +    }
    +    R->roster = obj;
    +    return TRUE;
    +}
    +
    +

    §4. The following schema validates the metadata for a registry, and is cached +so that it only needs to load once. +

    + +
    +dictionary *JSON_registry_metadata_requirements = NULL;
    +
    +JSON_requirement *Registries::requirements(void) {
    +    if (JSON_registry_metadata_requirements == NULL) {
    +        filename *F = InstalledFiles::filename(REGISTRY_JSON_REQS_IRES);
    +        JSON_registry_metadata_requirements = JSON::read_requirements_file(NULL, F);
    +    }
    +    JSON_requirement *req =
    +        JSON::look_up_requirements(JSON_registry_metadata_requirements, I"registry-metadata");
    +    if (req == NULL) internal_error("JSON metadata file did not define <registry-metadata>");
    +    return req;
    +}
    +
    +

    §5. Building. To "build" a registry doesn't involve very much: just putting some indexing +files together, using the preprocessor built into foundation. +

    + +

    If the registry is R, we preprocess each file R/source/X.Y into R/X.Y: +

    + +
    +void Registries::build(inbuild_registry *R) {
    +    if (R == NULL) internal_error("no registry");
    +    linked_list *ML = NEW_LINKED_LIST(preprocessor_macro);
    +    Construct the list of custom macros for this sort of preprocessing5.1;
    +    pathname *S = Pathnames::down(R->location, I"source");
    +    linked_list *L = Directories::listing(S);
    +    text_stream *entry;
    +    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
    +        if (Platform::is_folder_separator(Str::get_last_char(entry)) == FALSE) {
    +            filename *F = Filenames::in(S, entry);
    +            TEMPORARY_TEXT(EXT)
    +            Filenames::write_extension(EXT, F);
    +            if (Str::len(EXT) > 0) {
    +                filename *T = Filenames::in(R->location, Filenames::get_leafname(F));
    +                WRITE_TO(STDOUT, "%f -> %f\n", F, T);
    +                Preprocessor::preprocess(F, T, NULL, ML,
    +                    STORE_POINTER_inbuild_registry(R), '#', UTF8_ENC);
    +            }
    +            DISCARD_TEXT(EXT)
    +        }
    +    }
    +}
    +
    +

    §5.1. Construct the list of custom macros for this sort of preprocessing5.1 = +

    + +
    +    Preprocessor::new_macro(ML, I"include", I"file: LEAFNAME",
    +        Registries::include_expander, NULL);
    +    Preprocessor::new_macro(ML, I"process", I"file: LEAFNAME",
    +        Registries::process_expander, NULL);
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"section", I"of: ID",
    +        Registries::section_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"subsection", I"of: ID",
    +        Registries::subsection_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"author", I"of: ID ?escape: WHICH",
    +        Registries::author_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"title", I"of: ID ?escape: WHICH",
    +        Registries::title_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"version", I"of: ID ?escape: WHICH",
    +        Registries::version_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"summary", I"of: ID ?escape: WHICH",
    +        Registries::summary_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"forum-thread", I"of: ID",
    +        Registries::thread_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"section-mark", I"of: ID",
    +        Registries::section_mark_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"section-title", I"of: ID",
    +        Registries::section_title_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"subsection-mark", I"of: ID",
    +        Registries::subsection_mark_expander, NULL));
    +    Preprocessor::do_not_suppress_whitespace(
    +        Preprocessor::new_macro(ML, I"subsection-title", I"of: ID",
    +        Registries::subsection_title_expander, NULL));
    +    Preprocessor::new_loop_macro(ML, I"sections", NULL,
    +        Registries::sections_expander, NULL);
    +    Preprocessor::new_loop_macro(ML, I"subsections", I"in: SECTION",
    +        Registries::subsections_expander, NULL);
    +    Preprocessor::new_loop_macro(ML, I"resources", I"in: SECTION",
    +        Registries::resources_expander, NULL);
    +    Preprocessor::new_loop_macro(ML, I"if-forum-thread", I"for: ID",
    +        Registries::if_forum_thread_expander, NULL);
    +
    +
    • This code is used in §5.
    +

    §6. {include file:I} splices in the file R/source/include/I, unmodified. +It can contain any textual material, and even braces and backslashes pass +through exactly as written. +

    + +
    +void Registries::include_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *leafname = parameter_values[0];
    +    filename *prototype = Filenames::in(Pathnames::down(Pathnames::down(R->location, I"source"), I"include"), leafname);
    +    TextFiles::read(prototype, FALSE, "can't open include file",
    +        TRUE, Registries::scan_line, NULL, PPS);
    +}
    +
    +void Registries::scan_line(text_stream *line, text_file_position *tfp, void *X) {
    +    preprocessor_state *PPS = (preprocessor_state *) X;
    +    WRITE_TO(PPS->dest, "%S\n", line);
    +}
    +
    +

    §7. {process file:I} also splices in the file R/source/include/I, but runs +it through the preprocessor first. This means any macros it contains will be +expanded, and it has to comply with the syntax rules on use of braces and +backslash. +

    + +
    +void Registries::process_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *leafname = parameter_values[0];
    +    filename *prototype = Filenames::in(Pathnames::down(Pathnames::down(R->location, I"source"), I"include"), leafname);
    +    TextFiles::read(prototype, FALSE, "can't open include file",
    +        TRUE, Preprocessor::scan_line, NULL, PPS);
    +}
    +
    +

    §8. {sections} ... {end-sections} is a loop construct, which loops over each +section of the registry's roster file. The loop variable {SECTIONID} holds +the ID text for the section; right now, that's just 0, 1, 2, ... +

    + +
    +void Registries::sections_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    Preprocessor::set_loop_var_name(loop, I"SECTIONID");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int i = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        text_stream *sid = Str::new();
    +        WRITE_TO(sid, "%d", i++);
    +        Preprocessor::add_loop_iteration(loop, sid);
    +    }
    +}
    +
    +

    §9. {subsections in: SID} ... {end-subsections} loops similarly over all +subsections in the section with id SID. The loop variable is {SUBSECTIONID}. +This also now counts up from 0 (but textually: all preprocessor variables are +text), but note that this SSID is unique in the registry: i.e., it doesn't go back +to 0 at the start of each section. +

    + +
    +void Registries::subsections_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    text_stream *in = parameter_values[0];
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    Preprocessor::set_loop_var_name(loop, I"SUBSECTIONID");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int i = 0, j = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        TEMPORARY_TEXT(sid)
    +        WRITE_TO(sid, "%d", i++);
    +        JSON_value *subsections = JSON::look_up_object(E, I"subsections");
    +        if (subsections == NULL) internal_error("could not find roster subsections");
    +        JSON_value *F;
    +        LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) {
    +            if (Str::eq(sid, in)) {
    +                text_stream *ssid = Str::new();
    +                WRITE_TO(ssid, "%d", j);
    +                Preprocessor::add_loop_iteration(loop, ssid);
    +            }
    +            j++;
    +        }
    +        DISCARD_TEXT(sid)
    +    }
    +}
    +
    +

    §10. {resources in: SSID} ... {end-resources} loops similarly over all +resources in the subsection with id SSID, or over absolutely all resources +if the id is given as ALL. The loop variable is {ID}. +

    + +
    +void Registries::resources_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    text_stream *in = parameter_values[0];
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    Preprocessor::set_loop_var_name(loop, I"ID");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int j = 0, k = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        JSON_value *subsections = JSON::look_up_object(E, I"subsections");
    +        if (subsections == NULL) internal_error("could not find roster subsections");
    +        JSON_value *F;
    +        LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) {
    +            TEMPORARY_TEXT(ssid)
    +            WRITE_TO(ssid, "%d", j++);
    +            JSON_value *holdings = JSON::look_up_object(F, I"holdings");
    +            if (holdings == NULL) internal_error("could not find roster holdings");
    +            JSON_value *G;
    +            LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) {
    +                if ((Str::eq(in, I"ALL")) || (Str::eq(ssid, in))) {
    +                    text_stream *id = Str::new();
    +                    WRITE_TO(id, "%d", k);
    +                    Preprocessor::add_loop_iteration(loop, id);
    +                }
    +                k++;
    +            }
    +            DISCARD_TEXT(ssid)
    +        }
    +    }
    +}
    +
    +

    §11. We now have a run of macros which give details of the resource ID. +

    + +

    First, {section of: ID} produces the SID of its section. +

    + +
    +void Registries::section_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    TEMPORARY_TEXT(section)
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, section, NULL);
    +    if (res) WRITE_TO(PPS->dest, "%S", section);
    +    DISCARD_TEXT(section)
    +}
    +
    +

    §12. {subsection of: ID} produces the SSID of its subsection. +

    + +
    +void Registries::subsection_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    TEMPORARY_TEXT(subsection)
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, subsection);
    +    if (res) WRITE_TO(PPS->dest, "%S", subsection);
    +    DISCARD_TEXT(subsection)
    +}
    +
    +

    §13. {author of: ID escape: ESC} produces the author's name, optionally escaped +with the system below. +

    + +
    +void Registries::author_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    text_stream *escape = parameter_values[1];
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL);
    +    if (res) {
    +        JSON_value *author = JSON::look_up_object(res, I"author");
    +        if (author == NULL) internal_error("could not find author");
    +        Registries::write_escaped(PPS->dest, author->if_string, escape);
    +    }
    +}
    +
    +

    §14. {title of: ID escape: ESC} produces the title, optionally escaped with the +system below. +

    + +
    +void Registries::title_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    text_stream *escape = parameter_values[1];
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL);
    +    if (res) {
    +        JSON_value *title = JSON::look_up_object(res, I"title");
    +        if (title == NULL) internal_error("could not find title");
    +        Registries::write_escaped(PPS->dest, title->if_string, escape);
    +    }
    +}
    +
    +

    §15. {version of: ID escape: ESC} produces the version, optionally escaped with the +system below. +

    + +
    +void Registries::version_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    text_stream *escape = parameter_values[1];
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL);
    +    if (res) {
    +        JSON_value *version = JSON::look_up_object(res, I"version");
    +        if (version == NULL) internal_error("could not find version");
    +        Registries::write_escaped(PPS->dest, version->if_string, escape);
    +    }
    +}
    +
    +

    §16. {summary of: ID escape: ESC} produces the summary, optionally escaped with the +system below. +

    + +
    +void Registries::summary_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    text_stream *escape = parameter_values[1];
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL);
    +    if (res) {
    +        JSON_value *summary = JSON::look_up_object(res, I"summary");
    +        if (summary == NULL) internal_error("could not find summary");
    +        Registries::write_escaped(PPS->dest, summary->if_string, escape);
    +    }
    +}
    +
    +

    §17. {thread of: ID} produces the forum thread number, if it exists, and prints +nothing if it does not. +

    + +
    +void Registries::thread_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL);
    +    if (res) {
    +        JSON_value *thread = JSON::look_up_object(res, I"forum-thread");
    +        if (thread) WRITE_TO(PPS->dest, "%d", thread->if_integer);
    +    }
    +}
    +
    +

    §18. {if-forum-thread for: ID} ... {end-if-forum-thread} checks whether the +resource has a thread number, and if so, expands the material .... This is +crudely done as either a 0- or 1-term loop. +

    + +
    +void Registries::if_forum_thread_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    text_stream *of_id = parameter_values[0];
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    Preprocessor::set_loop_var_name(loop, I"ID");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int k = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        JSON_value *subsections = JSON::look_up_object(E, I"subsections");
    +        if (subsections == NULL) internal_error("could not find roster subsections");
    +        JSON_value *F;
    +        LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) {
    +            JSON_value *holdings = JSON::look_up_object(F, I"holdings");
    +            if (holdings == NULL) internal_error("could not find roster holdings");
    +            JSON_value *G;
    +            LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) {
    +                TEMPORARY_TEXT(id)
    +                WRITE_TO(id, "%d", k++);
    +                if (Str::eq(id, of_id)) {
    +                    JSON_value *thread = JSON::look_up_object(G, I"forum-thread");
    +                    if (thread) Preprocessor::add_loop_iteration(loop, id);
    +                }
    +                DISCARD_TEXT(id)
    +            }
    +        }
    +    }
    +}
    +
    +

    §19. {section-mark of: SID} produces the "section mark" of the section. +

    + +
    +void Registries::section_mark_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    TEMPORARY_TEXT(mark)
    +    JSON_value *section = Registries::section_from_textual_id(R, of, mark);
    +    if (section) WRITE_TO(PPS->dest, "%S", mark);
    +    DISCARD_TEXT(mark)
    +}
    +
    +

    §20. {section-title of: SID} produces the title of the section. +

    + +
    +void Registries::section_title_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    JSON_value *section = Registries::section_from_textual_id(R, of, NULL);
    +    if (section) {
    +        JSON_value *title = JSON::look_up_object(section, I"title");
    +        if (title == NULL) internal_error("could not find title");
    +        WRITE_TO(PPS->dest, "%S", title->if_string);
    +    }
    +}
    +
    +

    §21. {subsection-mark of: SID} produces the "subsection mark" of the subsection. +

    + +
    +void Registries::subsection_mark_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    TEMPORARY_TEXT(mark)
    +    JSON_value *subsection = Registries::subsection_from_textual_id(R, of, NULL, mark);
    +    if (subsection) WRITE_TO(PPS->dest, "%S", mark);
    +    DISCARD_TEXT(mark)
    +}
    +
    +

    §22. {subsection-title of: SID} produces the title of the subsection. +

    + +
    +void Registries::subsection_title_expander(preprocessor_macro *mm, preprocessor_state *PPS,
    +    text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
    +    inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics);
    +    text_stream *of = parameter_values[0];
    +    JSON_value *subsection = Registries::subsection_from_textual_id(R, of, NULL, NULL);
    +    if (subsection) {
    +        JSON_value *title = JSON::look_up_object(subsection, I"title");
    +        if (title == NULL) internal_error("could not find title");
    +        WRITE_TO(PPS->dest, "%S", title->if_string);
    +    }
    +}
    +
    +

    §23. Escapology. quotes escapes single quotation marks by placing a backslash before them, +as is necessary in JavaScript string literals. +

    + +

    spaces escapes spaces as %20, as is necessary in URLs. +

    + +

    both does both; neither does neither. +

    + +
    +void Registries::write_escaped(OUTPUT_STREAM, text_stream *text, text_stream *escape) {
    +    if (Str::eq(escape, I"quotes")) {
    +        LOOP_THROUGH_TEXT(pos, text) {
    +            wchar_t c = Str::get(pos);
    +            if (c == '\'') {
    +                PUT('\\');
    +                PUT('\'');
    +            } else {
    +                PUT(c);
    +            }
    +        }
    +    } else if (Str::eq(escape, I"spaces")) {
    +        LOOP_THROUGH_TEXT(pos, text) {
    +            wchar_t c = Str::get(pos);
    +            if (c == ' ') {
    +                WRITE("%%20");
    +            } else {
    +                PUT(c);
    +            }
    +        }
    +    } else if (Str::eq(escape, I"both")) {
    +        LOOP_THROUGH_TEXT(pos, text) {
    +            wchar_t c = Str::get(pos);
    +            if (c == '\'') {
    +                PUT('\\');
    +                PUT('\'');
    +            } else if (c == ' ') {
    +                WRITE("%%20");
    +            } else {
    +                PUT(c);
    +            }
    +        }
    +    } else if ((Str::eq(escape, I"neither")) || (Str::len(escape) == 0)) {
    +        WRITE("%S", text);
    +    } else WRITE_TO(STDERR, "error: no such escape as '%S'\n", escape);
    +}
    +
    +

    §24. Looking up by textual ID. Given a textual resource id id, return the JSON object for it, or else +print an error and return NULL. +

    + +

    On success, the SID of its section is written to sectionid, and the SSID +of its subsection to subsectionid. +

    + +
    +JSON_value *Registries::resource_from_textual_id(inbuild_registry *R, text_stream *id,
    +    text_stream *sectionid, text_stream *subsectionid) {
    +    if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int i = 0, j = 0, k = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        JSON_value *subsections = JSON::look_up_object(E, I"subsections");
    +        if (subsections == NULL) internal_error("could not find roster subsections");
    +        JSON_value *F;
    +        LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) {
    +            JSON_value *holdings = JSON::look_up_object(F, I"holdings");
    +            if (holdings == NULL) internal_error("could not find roster holdings");
    +            JSON_value *G;
    +            LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) {
    +                int match = FALSE;
    +                TEMPORARY_TEXT(this_id)
    +                WRITE_TO(this_id, "%d", k);
    +                if (Str::eq(id, this_id)) match = TRUE;
    +                DISCARD_TEXT(this_id)
    +                if (match) {
    +                    WRITE_TO(sectionid, "%d", i);
    +                    WRITE_TO(subsectionid, "%d", j);
    +                    return G;
    +                }
    +                k++;
    +            }
    +            j++;
    +        }
    +        i++;
    +    }
    +    WRITE_TO(STDERR, "error: no such resource ID as '%S'\n", id);
    +    return NULL;
    +}
    +
    +

    §25. Similarly for sections, with a SID. +

    + +

    The "mark" for a section is 1, 2, 3, ... +

    + +
    +JSON_value *Registries::section_from_textual_id(inbuild_registry *R, text_stream *sid,
    +    text_stream *mark) {
    +    if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int i = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        int match = FALSE;
    +        TEMPORARY_TEXT(this_sid)
    +        WRITE_TO(this_sid, "%d", i);
    +        if (Str::eq(sid, this_sid)) match = TRUE;
    +        DISCARD_TEXT(this_sid)
    +        if (match) {
    +            WRITE_TO(mark, "%d", i+1);
    +            return E;
    +        }
    +        i++;
    +    }
    +    WRITE_TO(STDERR, "error: no such section ID as '%S'\n", sid);
    +    return NULL;
    +}
    +
    +

    §26. And subsections, with a SSID: +

    + +

    The "mark" for a subsection is 1.1, 1.2, 1.3, ..., 2.1, 2.2, ... +

    + +
    +JSON_value *Registries::subsection_from_textual_id(inbuild_registry *R, text_stream *ssid,
    +    text_stream *mark, text_stream *submark) {
    +    if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry");
    +    JSON_value *sections = JSON::look_up_object(R->roster, I"sections");
    +    if (sections == NULL) internal_error("could not find roster sections");
    +    JSON_value *E;
    +    int i = 0, j = 0;
    +    LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) {
    +        JSON_value *subsections = JSON::look_up_object(E, I"subsections");
    +        if (subsections == NULL) internal_error("could not find roster subsections");
    +        int x = 1;
    +        JSON_value *F;
    +        LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) {
    +            int match = FALSE;
    +            TEMPORARY_TEXT(this_ssid)
    +            WRITE_TO(this_ssid, "%d", j);
    +            if (Str::eq(ssid, this_ssid)) match = TRUE;
    +            DISCARD_TEXT(this_ssid)
    +            if (match) {
    +                WRITE_TO(mark, "%d", i+1);
    +                WRITE_TO(submark, "%d.%d", i+1, x);
    +                return F;
    +            }
    +            j++, x++;
    +        }
    +        i++;
    +    }
    +    WRITE_TO(STDERR, "error: no such subsection ID as '%S'\n", ssid);
    +    return NULL;
    +}
    +
    + + +
    + + + diff --git a/docs/supervisor-module/2-rqr.html b/docs/supervisor-module/2-rqr.html index afda5aa1a..950cdd77e 100644 --- a/docs/supervisor-module/2-rqr.html +++ b/docs/supervisor-module/2-rqr.html @@ -284,7 +284,7 @@ imposes no version constraints. } diff --git a/docs/supervisor-module/2-wrk.html b/docs/supervisor-module/2-wrk.html index be13e0fb5..aee95cff8 100644 --- a/docs/supervisor-module/2-wrk.html +++ b/docs/supervisor-module/2-wrk.html @@ -337,7 +337,7 @@ each extension's page is generated from its
  • The function Works::escape_apostrophes appears nowhere else.
  • diff --git a/docs/supervisor-module/index.html b/docs/supervisor-module/index.html index 4c08c3ab0..4d82c8077 100644 --- a/docs/supervisor-module/index.html +++ b/docs/supervisor-module/index.html @@ -115,6 +115,11 @@ Nests - Nests are repositories of Inform-related resources.

    +
  • +

    + Registries - + Registries are nests provided with metadata and intended to be presented as an online source from which Inform resources can be downloaded.

    +
  • JSON Metadata - diff --git a/inbuild/Chapter 1/Main.w b/inbuild/Chapter 1/Main.w index 2bba4fa9f..fbbc18a3e 100644 --- a/inbuild/Chapter 1/Main.w +++ b/inbuild/Chapter 1/Main.w @@ -13,6 +13,7 @@ int inbuild_task = INSPECT_TTASK; pathname *path_to_tools = NULL; int dry_run_mode = FALSE, build_trace_mode = FALSE; inbuild_nest *destination_nest = NULL; +inbuild_registry *selected_registry = NULL; text_stream *filter_text = NULL; @h Main routine. @@ -311,6 +312,8 @@ other options to the selection defined here. @e COPY_TO_CLSW @e SYNC_TO_CLSW @e VERSIONS_IN_FILENAMES_CLSW +@e VERIFY_REGISTRY_CLSW +@e BUILD_REGISTRY_CLSW @ = CommandLine::declare_heading( @@ -356,6 +359,10 @@ other options to the selection defined here. L"apply to all works in nest(s) matching requirement X"); CommandLine::declare_switch(CONTENTS_OF_CLSW, L"contents-of", 2, L"apply to all targets in the directory X"); + CommandLine::declare_switch(VERIFY_REGISTRY_CLSW, L"verify-registry", 2, + L"verify roster.json metadata of registry in the directory X"); + CommandLine::declare_switch(BUILD_REGISTRY_CLSW, L"build-registry", 2, + L"construct HTML menu pages for registry in the directory X"); Supervisor::declare_options(); CommandLine::read(argc, argv, NULL, &Main::option, &Main::bareword); @@ -396,6 +403,13 @@ void Main::option(int id, int val, text_stream *arg, void *state) { break; case VERSIONS_IN_FILENAMES_CLSW: Editions::set_canonical_leaves_have_versions(val); break; + case VERIFY_REGISTRY_CLSW: + case BUILD_REGISTRY_CLSW: + selected_registry = Registries::new(Pathnames::from_text(arg)); + if (Registries::read_roster(selected_registry) == FALSE) exit(1); + if (id == BUILD_REGISTRY_CLSW) + Registries::build(selected_registry); + break; } Supervisor::option(id, val, arg, state); } diff --git a/inbuild/Contents.w b/inbuild/Contents.w index 9b2012c33..cc9156d14 100644 --- a/inbuild/Contents.w +++ b/inbuild/Contents.w @@ -18,6 +18,7 @@ Manual A Guide to Kits A Guide to Language Bundles A Guide to Project Metadata + A Guide to Registries Reference Card Chapter 1: Outside of inform7 diff --git a/inbuild/Figures/help.txt b/inbuild/Figures/help.txt index 6517cfc5a..e91d55e40 100644 --- a/inbuild/Figures/help.txt +++ b/inbuild/Figures/help.txt @@ -8,6 +8,7 @@ usage: inbuild [-TASK] TARGET1 TARGET2 ... -build-locate show file paths of all the extensions, kits and so on needed to build -build-missing show the extensions, kits and so on which are needed to build but missing -build-needs show all the extensions, kits and so on needed to build +-build-registry X construct HTML menu pages for registry in the directory X -build-trace show verbose reasoning during -build (default is -no-build-trace) -contents-of X apply to all targets in the directory X -copy-to X copy target(s) to nest X @@ -21,6 +22,7 @@ usage: inbuild [-TASK] TARGET1 TARGET2 ... -use-locate show file paths of all the extensions, kits and so on needed to use -use-missing show the extensions, kits and so on which are needed to use but missing -use-needs show all the extensions, kits and so on needed to use +-verify-registry X verify roster.json metadata of registry in the directory X -no-versions-in-filenames don't append _v number to destination filenames on -copy-to or -sync-to (default is -versions-in-filenames) for translating Inform source text to Inter: diff --git a/inbuild/Manual/A Guide to Registries.w b/inbuild/Manual/A Guide to Registries.w new file mode 100644 index 000000000..17256eb87 --- /dev/null +++ b/inbuild/Manual/A Guide to Registries.w @@ -0,0 +1,14 @@ +A Guide to Registries. + +Provisional documentation on how to scan and build registries of Inform resources. + +@h Registries. +A "registry" is a collection of Inform resources gathered together for others +to use. The Public Library presented in the Inform apps is a registry, for +example, but the idea is being generalised in inbuild with a view to future +developments. + +This page is a place-holder for now: see //supervisor: Registries// for +very slightly more. + +The PL presented as a registry can be seen at //this repository -> https://github.com/ganelson/inform-public-library//. diff --git a/inbuild/supervisor-module/Chapter 1/Supervisor Module.w b/inbuild/supervisor-module/Chapter 1/Supervisor Module.w index 365c5f017..93125f5ed 100644 --- a/inbuild/supervisor-module/Chapter 1/Supervisor Module.w +++ b/inbuild/supervisor-module/Chapter 1/Supervisor Module.w @@ -27,6 +27,7 @@ which use this module: @e inbuild_edition_CLASS @e inbuild_genre_CLASS @e inbuild_nest_CLASS +@e inbuild_registry_CLASS @e inbuild_requirement_CLASS @e inbuild_search_result_CLASS @e inbuild_work_CLASS @@ -58,6 +59,7 @@ DECLARE_CLASS(inbuild_copy) DECLARE_CLASS(inbuild_edition) DECLARE_CLASS(inbuild_genre) DECLARE_CLASS(inbuild_nest) +DECLARE_CLASS(inbuild_registry) DECLARE_CLASS(inbuild_requirement) DECLARE_CLASS(inbuild_search_result) DECLARE_CLASS(inbuild_work) diff --git a/inbuild/supervisor-module/Chapter 2/JSON Metadata.w b/inbuild/supervisor-module/Chapter 2/JSON Metadata.w index 6d16df102..7e434cbbc 100644 --- a/inbuild/supervisor-module/Chapter 2/JSON Metadata.w +++ b/inbuild/supervisor-module/Chapter 2/JSON Metadata.w @@ -237,7 +237,7 @@ dictionary *JSON_resource_metadata_requirements = NULL; JSON_requirement *JSONMetadata::requirements(void) { if (JSON_resource_metadata_requirements == NULL) { - filename *F = InstalledFiles::filename(JSON_REQUIREMENTS_IRES); + filename *F = InstalledFiles::filename(RESOURCE_JSON_REQS_IRES); JSON_resource_metadata_requirements = JSON::read_requirements_file(NULL, F); } JSON_requirement *req = diff --git a/inbuild/supervisor-module/Chapter 2/Registries.w b/inbuild/supervisor-module/Chapter 2/Registries.w new file mode 100644 index 000000000..99c6c3ae2 --- /dev/null +++ b/inbuild/supervisor-module/Chapter 2/Registries.w @@ -0,0 +1,641 @@ +[Registries::] Registries. + +Registries are nests provided with metadata and intended to be presented as +an online source from which Inform resources can be downloaded. + +@h Creation. +To "create" a registry here does not mean actually altering the file system, for +example by making a directory: registries here are merely notes in memory of +positions in the file system hierarchy which may or may not exist. + += +typedef struct inbuild_registry { + struct pathname *location; + struct inbuild_nest *nest; + struct JSON_value *roster; + CLASS_DEFINITION +} inbuild_registry; + +@ = +inbuild_registry *Registries::new(pathname *P) { + inbuild_registry *N = CREATE(inbuild_registry); + N->location = P; + N->nest = Nests::new(Pathnames::down(P, I"payloads")); + N->roster = NULL; + return N; +} + +@h The roster. +This is a JSON file called |roster.json|, whose schema must match the one specified +by the file |registry-metadata.jsonr| in the standard Inform distribution. + +The following silently returns |TRUE| if it does, or prints errors and returns +|FALSE| if not (or if it doesn't exist). + += +int Registries::read_roster(inbuild_registry *R) { + if (R == NULL) internal_error("no registry"); + R->roster = NULL; + filename *F = Filenames::in(R->location, I"roster.json"); + if (TextFiles::exists(F) == FALSE) { + WRITE_TO(STDERR, "%f: roster file does not exist\n", F); + return FALSE; + } + TEMPORARY_TEXT(contents) + TextFiles::read(F, FALSE, "unable to read file of JSON metadata", TRUE, + &JSONMetadata::read_metadata_file_helper, NULL, contents); + text_file_position tfp = TextFiles::at(F, 1); + JSON_value *obj = JSON::decode(contents, &tfp); + DISCARD_TEXT(contents) + if ((obj) && (obj->JSON_type == ERROR_JSONTYPE)) { + WRITE_TO(STDERR, "%f: JSON syntax error: %S\n", F, obj->if_error); + return FALSE; + } else { + JSON_requirement *req = Registries::requirements(); + linked_list *validation_errors = NEW_LINKED_LIST(text_stream); + if (JSON::validate(obj, req, validation_errors) == FALSE) { + text_stream *err; + LOOP_OVER_LINKED_LIST(err, text_stream, validation_errors) { + WRITE_TO(STDERR, "%f: metadata did not validate: '%S'\n", F, err); + } + return FALSE; + } + } + R->roster = obj; + return TRUE; +} + +@ The following schema validates the metadata for a registry, and is cached +so that it only needs to load once. + += +dictionary *JSON_registry_metadata_requirements = NULL; + +JSON_requirement *Registries::requirements(void) { + if (JSON_registry_metadata_requirements == NULL) { + filename *F = InstalledFiles::filename(REGISTRY_JSON_REQS_IRES); + JSON_registry_metadata_requirements = JSON::read_requirements_file(NULL, F); + } + JSON_requirement *req = + JSON::look_up_requirements(JSON_registry_metadata_requirements, I"registry-metadata"); + if (req == NULL) internal_error("JSON metadata file did not define "); + return req; +} + +@h Building. +To "build" a registry doesn't involve very much: just putting some indexing +files together, using the preprocessor built into //foundation//. + +If the registry is |R|, we preprocess each file |R/source/X.Y| into |R/X.Y|: + += +void Registries::build(inbuild_registry *R) { + if (R == NULL) internal_error("no registry"); + linked_list *ML = NEW_LINKED_LIST(preprocessor_macro); + @; + pathname *S = Pathnames::down(R->location, I"source"); + linked_list *L = Directories::listing(S); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + if (Platform::is_folder_separator(Str::get_last_char(entry)) == FALSE) { + filename *F = Filenames::in(S, entry); + TEMPORARY_TEXT(EXT) + Filenames::write_extension(EXT, F); + if (Str::len(EXT) > 0) { + filename *T = Filenames::in(R->location, Filenames::get_leafname(F)); + WRITE_TO(STDOUT, "%f -> %f\n", F, T); + Preprocessor::preprocess(F, T, NULL, ML, + STORE_POINTER_inbuild_registry(R), '#', UTF8_ENC); + } + DISCARD_TEXT(EXT) + } + } +} + +@ = + Preprocessor::new_macro(ML, I"include", I"file: LEAFNAME", + Registries::include_expander, NULL); + Preprocessor::new_macro(ML, I"process", I"file: LEAFNAME", + Registries::process_expander, NULL); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"section", I"of: ID", + Registries::section_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"subsection", I"of: ID", + Registries::subsection_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"author", I"of: ID ?escape: WHICH", + Registries::author_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"title", I"of: ID ?escape: WHICH", + Registries::title_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"version", I"of: ID ?escape: WHICH", + Registries::version_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"summary", I"of: ID ?escape: WHICH", + Registries::summary_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"forum-thread", I"of: ID", + Registries::thread_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"section-mark", I"of: ID", + Registries::section_mark_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"section-title", I"of: ID", + Registries::section_title_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"subsection-mark", I"of: ID", + Registries::subsection_mark_expander, NULL)); + Preprocessor::do_not_suppress_whitespace( + Preprocessor::new_macro(ML, I"subsection-title", I"of: ID", + Registries::subsection_title_expander, NULL)); + Preprocessor::new_loop_macro(ML, I"sections", NULL, + Registries::sections_expander, NULL); + Preprocessor::new_loop_macro(ML, I"subsections", I"in: SECTION", + Registries::subsections_expander, NULL); + Preprocessor::new_loop_macro(ML, I"resources", I"in: SECTION", + Registries::resources_expander, NULL); + Preprocessor::new_loop_macro(ML, I"if-forum-thread", I"for: ID", + Registries::if_forum_thread_expander, NULL); + +@ |{include file:I}| splices in the file |R/source/include/I|, unmodified. +It can contain any textual material, and even braces and backslashes pass +through exactly as written. + += +void Registries::include_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *leafname = parameter_values[0]; + filename *prototype = Filenames::in(Pathnames::down(Pathnames::down(R->location, I"source"), I"include"), leafname); + TextFiles::read(prototype, FALSE, "can't open include file", + TRUE, Registries::scan_line, NULL, PPS); +} + +void Registries::scan_line(text_stream *line, text_file_position *tfp, void *X) { + preprocessor_state *PPS = (preprocessor_state *) X; + WRITE_TO(PPS->dest, "%S\n", line); +} + +@ |{process file:I}| also splices in the file |R/source/include/I|, but runs +it through the preprocessor first. This means any macros it contains will be +expanded, and it has to comply with the syntax rules on use of braces and +backslash. + += +void Registries::process_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *leafname = parameter_values[0]; + filename *prototype = Filenames::in(Pathnames::down(Pathnames::down(R->location, I"source"), I"include"), leafname); + TextFiles::read(prototype, FALSE, "can't open include file", + TRUE, Preprocessor::scan_line, NULL, PPS); +} + +@ |{sections}| ... |{end-sections}| is a loop construct, which loops over each +section of the registry's roster file. The loop variable |{SECTIONID}| holds +the ID text for the section; right now, that's just |0|, |1|, |2|, ... + += +void Registries::sections_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + Preprocessor::set_loop_var_name(loop, I"SECTIONID"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int i = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + text_stream *sid = Str::new(); + WRITE_TO(sid, "%d", i++); + Preprocessor::add_loop_iteration(loop, sid); + } +} + +@ |{subsections in: SID}| ... |{end-subsections}| loops similarly over all +subsections in the section with id |SID|. The loop variable is |{SUBSECTIONID}|. +This also now counts up from 0 (but textually: all preprocessor variables are +text), but note that this SSID is unique in the registry: i.e., it doesn't go back +to |0| at the start of each section. + += +void Registries::subsections_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + text_stream *in = parameter_values[0]; + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + Preprocessor::set_loop_var_name(loop, I"SUBSECTIONID"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int i = 0, j = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + TEMPORARY_TEXT(sid) + WRITE_TO(sid, "%d", i++); + JSON_value *subsections = JSON::look_up_object(E, I"subsections"); + if (subsections == NULL) internal_error("could not find roster subsections"); + JSON_value *F; + LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { + if (Str::eq(sid, in)) { + text_stream *ssid = Str::new(); + WRITE_TO(ssid, "%d", j); + Preprocessor::add_loop_iteration(loop, ssid); + } + j++; + } + DISCARD_TEXT(sid) + } +} + +@ |{resources in: SSID}| ... |{end-resources}| loops similarly over all +resources in the subsection with id |SSID|, or over absolutely all resources +if the id is given as |ALL|. The loop variable is |{ID}|. + += +void Registries::resources_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + text_stream *in = parameter_values[0]; + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + Preprocessor::set_loop_var_name(loop, I"ID"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int j = 0, k = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + JSON_value *subsections = JSON::look_up_object(E, I"subsections"); + if (subsections == NULL) internal_error("could not find roster subsections"); + JSON_value *F; + LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { + TEMPORARY_TEXT(ssid) + WRITE_TO(ssid, "%d", j++); + JSON_value *holdings = JSON::look_up_object(F, I"holdings"); + if (holdings == NULL) internal_error("could not find roster holdings"); + JSON_value *G; + LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) { + if ((Str::eq(in, I"ALL")) || (Str::eq(ssid, in))) { + text_stream *id = Str::new(); + WRITE_TO(id, "%d", k); + Preprocessor::add_loop_iteration(loop, id); + } + k++; + } + DISCARD_TEXT(ssid) + } + } +} + +@ We now have a run of macros which give details of the resource |ID|. + +First, |{section of: ID}| produces the SID of its section. + += +void Registries::section_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + TEMPORARY_TEXT(section) + JSON_value *res = Registries::resource_from_textual_id(R, of, section, NULL); + if (res) WRITE_TO(PPS->dest, "%S", section); + DISCARD_TEXT(section) +} + +@ |{subsection of: ID}| produces the SSID of its subsection. + += +void Registries::subsection_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + TEMPORARY_TEXT(subsection) + JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, subsection); + if (res) WRITE_TO(PPS->dest, "%S", subsection); + DISCARD_TEXT(subsection) +} + +@ |{author of: ID escape: ESC}| produces the author's name, optionally escaped +with the system below. + += +void Registries::author_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + text_stream *escape = parameter_values[1]; + JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); + if (res) { + JSON_value *author = JSON::look_up_object(res, I"author"); + if (author == NULL) internal_error("could not find author"); + Registries::write_escaped(PPS->dest, author->if_string, escape); + } +} + +@ |{title of: ID escape: ESC}| produces the title, optionally escaped with the +system below. + += +void Registries::title_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + text_stream *escape = parameter_values[1]; + JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); + if (res) { + JSON_value *title = JSON::look_up_object(res, I"title"); + if (title == NULL) internal_error("could not find title"); + Registries::write_escaped(PPS->dest, title->if_string, escape); + } +} + +@ |{version of: ID escape: ESC}| produces the version, optionally escaped with the +system below. + += +void Registries::version_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + text_stream *escape = parameter_values[1]; + JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); + if (res) { + JSON_value *version = JSON::look_up_object(res, I"version"); + if (version == NULL) internal_error("could not find version"); + Registries::write_escaped(PPS->dest, version->if_string, escape); + } +} + +@ |{summary of: ID escape: ESC}| produces the summary, optionally escaped with the +system below. + += +void Registries::summary_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + text_stream *escape = parameter_values[1]; + JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); + if (res) { + JSON_value *summary = JSON::look_up_object(res, I"summary"); + if (summary == NULL) internal_error("could not find summary"); + Registries::write_escaped(PPS->dest, summary->if_string, escape); + } +} + +@ |{thread of: ID}| produces the forum thread number, if it exists, and prints +nothing if it does not. + += +void Registries::thread_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); + if (res) { + JSON_value *thread = JSON::look_up_object(res, I"forum-thread"); + if (thread) WRITE_TO(PPS->dest, "%d", thread->if_integer); + } +} + +@ |{if-forum-thread for: ID}| ... |{end-if-forum-thread}| checks whether the +resource has a thread number, and if so, expands the material |...|. This is +crudely done as either a 0- or 1-term loop. + += +void Registries::if_forum_thread_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + text_stream *of_id = parameter_values[0]; + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + Preprocessor::set_loop_var_name(loop, I"ID"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int k = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + JSON_value *subsections = JSON::look_up_object(E, I"subsections"); + if (subsections == NULL) internal_error("could not find roster subsections"); + JSON_value *F; + LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { + JSON_value *holdings = JSON::look_up_object(F, I"holdings"); + if (holdings == NULL) internal_error("could not find roster holdings"); + JSON_value *G; + LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) { + TEMPORARY_TEXT(id) + WRITE_TO(id, "%d", k++); + if (Str::eq(id, of_id)) { + JSON_value *thread = JSON::look_up_object(G, I"forum-thread"); + if (thread) Preprocessor::add_loop_iteration(loop, id); + } + DISCARD_TEXT(id) + } + } + } +} + +@ |{section-mark of: SID}| produces the "section mark" of the section. + += +void Registries::section_mark_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + TEMPORARY_TEXT(mark) + JSON_value *section = Registries::section_from_textual_id(R, of, mark); + if (section) WRITE_TO(PPS->dest, "%S", mark); + DISCARD_TEXT(mark) +} + +@ |{section-title of: SID}| produces the title of the section. + += +void Registries::section_title_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + JSON_value *section = Registries::section_from_textual_id(R, of, NULL); + if (section) { + JSON_value *title = JSON::look_up_object(section, I"title"); + if (title == NULL) internal_error("could not find title"); + WRITE_TO(PPS->dest, "%S", title->if_string); + } +} + +@ |{subsection-mark of: SID}| produces the "subsection mark" of the subsection. + += +void Registries::subsection_mark_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + TEMPORARY_TEXT(mark) + JSON_value *subsection = Registries::subsection_from_textual_id(R, of, NULL, mark); + if (subsection) WRITE_TO(PPS->dest, "%S", mark); + DISCARD_TEXT(mark) +} + +@ |{subsection-title of: SID}| produces the title of the subsection. + += +void Registries::subsection_title_expander(preprocessor_macro *mm, preprocessor_state *PPS, + text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { + inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); + text_stream *of = parameter_values[0]; + JSON_value *subsection = Registries::subsection_from_textual_id(R, of, NULL, NULL); + if (subsection) { + JSON_value *title = JSON::look_up_object(subsection, I"title"); + if (title == NULL) internal_error("could not find title"); + WRITE_TO(PPS->dest, "%S", title->if_string); + } +} + +@h Escapology. +|quotes| escapes single quotation marks by placing a backslash before them, +as is necessary in JavaScript string literals. + +|spaces| escapes spaces as |%20|, as is necessary in URLs. + +|both| does both; |neither| does neither. + += +void Registries::write_escaped(OUTPUT_STREAM, text_stream *text, text_stream *escape) { + if (Str::eq(escape, I"quotes")) { + LOOP_THROUGH_TEXT(pos, text) { + wchar_t c = Str::get(pos); + if (c == '\'') { + PUT('\\'); + PUT('\''); + } else { + PUT(c); + } + } + } else if (Str::eq(escape, I"spaces")) { + LOOP_THROUGH_TEXT(pos, text) { + wchar_t c = Str::get(pos); + if (c == ' ') { + WRITE("%%20"); + } else { + PUT(c); + } + } + } else if (Str::eq(escape, I"both")) { + LOOP_THROUGH_TEXT(pos, text) { + wchar_t c = Str::get(pos); + if (c == '\'') { + PUT('\\'); + PUT('\''); + } else if (c == ' ') { + WRITE("%%20"); + } else { + PUT(c); + } + } + } else if ((Str::eq(escape, I"neither")) || (Str::len(escape) == 0)) { + WRITE("%S", text); + } else WRITE_TO(STDERR, "error: no such escape as '%S'\n", escape); +} + +@h Looking up by textual ID. +Given a textual resource id |id|, return the JSON object for it, or else +print an error and return |NULL|. + +On success, the SID of its section is written to |sectionid|, and the SSID +of its subsection to |subsectionid|. + += +JSON_value *Registries::resource_from_textual_id(inbuild_registry *R, text_stream *id, + text_stream *sectionid, text_stream *subsectionid) { + if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int i = 0, j = 0, k = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + JSON_value *subsections = JSON::look_up_object(E, I"subsections"); + if (subsections == NULL) internal_error("could not find roster subsections"); + JSON_value *F; + LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { + JSON_value *holdings = JSON::look_up_object(F, I"holdings"); + if (holdings == NULL) internal_error("could not find roster holdings"); + JSON_value *G; + LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) { + int match = FALSE; + TEMPORARY_TEXT(this_id) + WRITE_TO(this_id, "%d", k); + if (Str::eq(id, this_id)) match = TRUE; + DISCARD_TEXT(this_id) + if (match) { + WRITE_TO(sectionid, "%d", i); + WRITE_TO(subsectionid, "%d", j); + return G; + } + k++; + } + j++; + } + i++; + } + WRITE_TO(STDERR, "error: no such resource ID as '%S'\n", id); + return NULL; +} + +@ Similarly for sections, with a |SID|. + +The "mark" for a section is 1, 2, 3, ... + += +JSON_value *Registries::section_from_textual_id(inbuild_registry *R, text_stream *sid, + text_stream *mark) { + if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int i = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + int match = FALSE; + TEMPORARY_TEXT(this_sid) + WRITE_TO(this_sid, "%d", i); + if (Str::eq(sid, this_sid)) match = TRUE; + DISCARD_TEXT(this_sid) + if (match) { + WRITE_TO(mark, "%d", i+1); + return E; + } + i++; + } + WRITE_TO(STDERR, "error: no such section ID as '%S'\n", sid); + return NULL; +} + +@ And subsections, with a |SSID|: + +The "mark" for a subsection is 1.1, 1.2, 1.3, ..., 2.1, 2.2, ... + += +JSON_value *Registries::subsection_from_textual_id(inbuild_registry *R, text_stream *ssid, + text_stream *mark, text_stream *submark) { + if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry"); + JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); + if (sections == NULL) internal_error("could not find roster sections"); + JSON_value *E; + int i = 0, j = 0; + LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { + JSON_value *subsections = JSON::look_up_object(E, I"subsections"); + if (subsections == NULL) internal_error("could not find roster subsections"); + int x = 1; + JSON_value *F; + LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { + int match = FALSE; + TEMPORARY_TEXT(this_ssid) + WRITE_TO(this_ssid, "%d", j); + if (Str::eq(ssid, this_ssid)) match = TRUE; + DISCARD_TEXT(this_ssid) + if (match) { + WRITE_TO(mark, "%d", i+1); + WRITE_TO(submark, "%d.%d", i+1, x); + return F; + } + j++, x++; + } + i++; + } + WRITE_TO(STDERR, "error: no such subsection ID as '%S'\n", ssid); + return NULL; +} diff --git a/inbuild/supervisor-module/Contents.w b/inbuild/supervisor-module/Contents.w index a64722cf1..f36794088 100644 --- a/inbuild/supervisor-module/Contents.w +++ b/inbuild/supervisor-module/Contents.w @@ -19,6 +19,7 @@ Chapter 2: Conceptual Framework Copy Errors Requirements Nests + Registries JSON Metadata Chapter 3: Incremental Builds diff --git a/inform7/Figures/memory-diagnostics.txt b/inform7/Figures/memory-diagnostics.txt index 305f03740..beb0ddf80 100644 --- a/inform7/Figures/memory-diagnostics.txt +++ b/inform7/Figures/memory-diagnostics.txt @@ -1,6 +1,6 @@ -Total memory consumption was 120981K = 118 MB +Total memory consumption was 120989K = 118 MB - ---- was used for 2048236 objects, in 364575 frames in 0 x 800K = 0K = 0 MB: + ---- was used for 2048237 objects, in 364576 frames in 0 x 800K = 0K = 0 MB: 33.7% inter_tree_node_array 58 x 8192 = 475136 objects, 41813824 bytes 21.0% text_stream_array 4620 x 100 = 462000 objects, 26019840 bytes @@ -44,7 +44,7 @@ Total memory consumption was 120981K = 118 MB 0.2% inference_subject 666 objects, 261072 bytes 0.1% vanilla_function 3683 objects, 235712 bytes 0.1% binary_predicate 322 objects, 170016 bytes - 0.1% hierarchy_location 1123 objects, 161712 bytes + 0.1% hierarchy_location 1124 objects, 161856 bytes 0.1% linguistic_stock_item 3318 objects, 159264 bytes 0.1% rule_family_data 401 objects, 147568 bytes 0.1% nonterminal 758 objects, 139472 bytes @@ -59,7 +59,7 @@ Total memory consumption was 120981K = 118 MB ---- lexical_cluster 2519 objects, 80608 bytes ---- pcalc_term_array 2 x 1000 = 2000 objects, 80064 bytes ---- kind_variable_declaration 1655 objects, 79440 bytes - ---- inter_tree 6 objects, 79296 bytes + ---- inter_tree 6 objects, 79344 bytes ---- label_namespace 1472 objects, 70656 bytes ---- rulebook 407 objects, 68376 bytes ---- spatial_data 671 objects, 64416 bytes @@ -79,8 +79,8 @@ Total memory consumption was 120981K = 118 MB ---- ap_clause_array 2 x 400 = 800 objects, 51264 bytes ---- HTML_tag_array 1 x 1000 objects, 48032 bytes ---- text_substitution 437 objects, 41952 bytes - ---- activity_list_array 1 x 1000 objects, 40032 bytes ---- anl_clause_array 1 x 1000 objects, 40032 bytes + ---- activity_list_array 1 x 1000 objects, 40032 bytes ---- to_family_data 497 objects, 39760 bytes ---- shared_variable_access_list_array 12 x 100 = 1200 objects, 38784 bytes ---- parsing_data 671 objects, 37576 bytes @@ -107,8 +107,8 @@ Total memory consumption was 120981K = 118 MB ---- parse_node_tree 20 objects, 17280 bytes ---- understanding_reference_array 2 x 100 = 200 objects, 16064 bytes ---- to_phrase_request 59 objects, 16048 bytes - ---- match_avinue_array 1 x 1000 objects, 16032 bytes ---- action_name_list_array 1 x 1000 objects, 16032 bytes + ---- match_avinue_array 1 x 1000 objects, 16032 bytes ---- JSON_value 182 objects, 16016 bytes ---- build_vertex 130 objects, 15600 bytes ---- adjective 137 objects, 15344 bytes @@ -137,8 +137,8 @@ Total memory consumption was 120981K = 118 MB ---- section_md 45 objects, 4320 bytes ---- build_script 130 objects, 4160 bytes ---- compatibility_specification 86 objects, 4128 bytes - ---- activity 35 objects, 3920 bytes ---- command_line_switch 49 objects, 3920 bytes + ---- activity 35 objects, 3920 bytes ---- submodule_request 94 objects, 3760 bytes ---- parse_node_annotation_type 114 objects, 3648 bytes ---- property_setting_bp_data 84 objects, 3360 bytes @@ -183,8 +183,8 @@ Total memory consumption was 120981K = 118 MB ---- JSON_pair_requirement 26 objects, 832 bytes ---- phrase_option_array 1 x 100 objects, 824 bytes ---- inbuild_search_result 20 objects, 800 bytes - ---- web_md 5 objects, 720 bytes ---- internal_test 15 objects, 720 bytes + ---- web_md 5 objects, 720 bytes ---- relation_guard 5 objects, 640 bytes ---- implication 13 objects, 624 bytes ---- code_generation 1 object, 576 bytes @@ -192,42 +192,42 @@ Total memory consumption was 120981K = 118 MB ---- rulebook_outcome 17 objects, 544 bytes ---- small_word_set 11 objects, 528 bytes ---- inform_kit 5 objects, 520 bytes - ---- equation 4 objects, 480 bytes ---- inform_language 6 objects, 480 bytes + ---- equation 4 objects, 480 bytes ---- i6_memory_setting 14 objects, 448 bytes - ---- inference_family 11 objects, 440 bytes ---- chapter_md 5 objects, 440 bytes + ---- inference_family 11 objects, 440 bytes ---- bp_family 13 objects, 416 bytes - ---- module 5 objects, 400 bytes ---- inter_annotation_form 10 objects, 400 bytes + ---- module 5 objects, 400 bytes ---- article_usage 8 objects, 384 bytes ---- source_file 5 objects, 360 bytes ---- inbuild_genre 7 objects, 336 bytes + ---- cached_kind_declaration 8 objects, 320 bytes + ---- module_request 8 objects, 320 bytes ---- grammatical_category 8 objects, 320 bytes ---- pronoun 8 objects, 320 bytes - ---- cached_kind_declaration 8 objects, 320 bytes ---- door_dir_notice 5 objects, 320 bytes - ---- module_request 8 objects, 320 bytes - ---- tree_inventory 1 object, 312 bytes ---- inter_pipeline 1 object, 312 bytes + ---- tree_inventory 1 object, 312 bytes ---- up_family 9 objects, 288 bytes - ---- compilation_unit 5 objects, 280 bytes ---- explicit_bp_data 5 objects, 280 bytes - ---- contents_entry 7 objects, 280 bytes ---- door_to_notice 5 objects, 280 bytes + ---- contents_entry 7 objects, 280 bytes + ---- compilation_unit 5 objects, 280 bytes ---- verb_usage_tier 5 objects, 240 bytes ---- adjective_meaning_family 7 objects, 224 bytes - ---- test_scenario 1 object, 216 bytes ---- inform_project 1 object, 216 bytes + ---- test_scenario 1 object, 216 bytes ---- release_instructions 1 object, 208 bytes - ---- code_generator 5 objects, 200 bytes ---- build_skill 5 objects, 200 bytes + ---- code_generator 5 objects, 200 bytes ---- plural_dictionary_entry 4 objects, 192 bytes ---- kit_dependency 4 objects, 192 bytes - ---- inference_subject_family 5 objects, 160 bytes ---- inter_architecture 4 objects, 160 bytes - ---- imperative_defn_family 4 objects, 160 bytes ---- attachment_instruction 4 objects, 160 bytes + ---- imperative_defn_family 4 objects, 160 bytes + ---- inference_subject_family 5 objects, 160 bytes ---- element_activation 4 objects, 128 bytes ---- inbuild_nest 3 objects, 120 bytes ---- local_block_value 2 objects, 112 bytes @@ -235,19 +235,19 @@ Total memory consumption was 120981K = 118 MB ---- group_together_function 2 objects, 80 bytes ---- compile_task_data 1 object, 80 bytes ---- article 2 objects, 80 bytes - ---- figures_data 1 object, 56 bytes ---- inter_warehouse 1 object, 56 bytes + ---- figures_data 1 object, 56 bytes ---- build_methodology 1 object, 56 bytes - ---- star_invention 1 object, 48 bytes ---- HTML_file_state 1 object, 48 bytes - ---- loop_over_scope 1 object, 40 bytes + ---- star_invention 1 object, 48 bytes + ---- kind_template_definition 1 object, 40 bytes ---- I6_generation_data 1 object, 40 bytes ---- by_function_bp_data 1 object, 40 bytes - ---- kind_template_definition 1 object, 40 bytes + ---- loop_over_scope 1 object, 40 bytes 100.0% was used for memory not allocated for objects: - 56.8% text stream storage 70447848 bytes in 479132 claims + 56.8% text stream storage 70455588 bytes in 479200 claims 4.2% dictionary storage 5263872 bytes in 7588 claims ---- sorting 1520 bytes in 159 claims 5.8% source text 7200000 bytes in 3 claims @@ -265,5 +265,5 @@ Total memory consumption was 120981K = 118 MB ---- code generation workspace for objects 1336 bytes in 4 claims 0.2% emitter array storage 280288 bytes in 1999 claims --150.0% was overhead - -185923840 bytes = -181566K = -177 MB +-150.0% was overhead - -185924032 bytes = -181566K = -177 MB diff --git a/inform7/Figures/timings-diagnostics.txt b/inform7/Figures/timings-diagnostics.txt index 02e59fec0..7b4506d37 100644 --- a/inform7/Figures/timings-diagnostics.txt +++ b/inform7/Figures/timings-diagnostics.txt @@ -1,7 +1,7 @@ 100.0% in inform7 run 70.4% in compilation to Inter - 50.5% in //Sequence::undertake_queued_tasks// - 4.4% in //MajorNodes::pre_pass// + 50.0% in //Sequence::undertake_queued_tasks// + 4.6% in //MajorNodes::pre_pass// 3.4% in //MajorNodes::pass_1// 1.8% in //ImperativeDefinitions::assess_all// 1.4% in //RTKindConstructors::compile// @@ -17,17 +17,17 @@ 0.2% in //RTKindConstructors::compile_permissions// 0.2% in //Task::make_built_in_kind_constructors// 0.2% in //World::stages_II_and_III// - 2.6% not specifically accounted for - 26.6% in running Inter pipeline + 3.0% not specifically accounted for + 26.3% in running Inter pipeline 10.3% in step 14/15: generate inform6 -> auto.inf - 5.9% in step 5/15: load-binary-kits - 5.4% in step 6/15: make-synoptic-module - 1.4% in step 9/15: make-identifiers-unique + 5.6% in step 5/15: load-binary-kits + 5.6% in step 6/15: make-synoptic-module + 1.6% in step 9/15: make-identifiers-unique 0.4% in step 12/15: eliminate-redundant-operations 0.4% in step 4/15: compile-splats 0.4% in step 7/15: shorten-wiring 0.4% in step 8/15: detect-indirect-calls 0.2% in step 11/15: eliminate-redundant-labels - 1.6% not specifically accounted for + 1.2% not specifically accounted for 2.6% in supervisor - 0.3% not specifically accounted for + 0.7% not specifically accounted for diff --git a/inform7/Internal/Miscellany/registry.jsonr b/inform7/Internal/Miscellany/registry.jsonr new file mode 100644 index 000000000..70523a265 --- /dev/null +++ b/inform7/Internal/Miscellany/registry.jsonr @@ -0,0 +1,25 @@ + ::= { + "author": string, + "title": string, + ?"standard-installation": boolean, + ?"visible": boolean, + ?"forum-thread": number, + "version": string, + "summary": string, + "time": string +} + + ::= { + "title": string, + "holdings": [ * ] +} + + ::= { + "title": string, + "leafname": string, + "subsections": [ * ] +} + + ::= { + "sections": [ * ] +} diff --git a/inform7/Internal/Miscellany/metadata.jsonr b/inform7/Internal/Miscellany/resource.jsonr similarity index 100% rename from inform7/Internal/Miscellany/metadata.jsonr rename to inform7/Internal/Miscellany/resource.jsonr diff --git a/services/html-module/Chapter 2/Installed Files.w b/services/html-module/Chapter 2/Installed Files.w index 0608f8e62..fcb7cf836 100644 --- a/services/html-module/Chapter 2/Installed Files.w +++ b/services/html-module/Chapter 2/Installed Files.w @@ -21,7 +21,8 @@ but they're just plain old files, and are not managed by Inbuild as "copies". @e CSS_SET_BY_PLATFORM_IRES @e CSS_FOR_STANDARD_PAGES_IRES @e EXTENSION_DOCUMENTATION_MODEL_IRES -@e JSON_REQUIREMENTS_IRES +@e RESOURCE_JSON_REQS_IRES +@e REGISTRY_JSON_REQS_IRES = filename *InstalledFiles::filename(int ires) { @@ -39,8 +40,10 @@ filename *InstalledFiles::filename(int ires) { return Filenames::in(misc, I"DefaultCover.jpg"); case SMALL_DEFAULT_COVER_ART_IRES: return Filenames::in(misc, I"Small Cover.jpg"); - case JSON_REQUIREMENTS_IRES: - return Filenames::in(misc, I"metadata.jsonr"); + case RESOURCE_JSON_REQS_IRES: + return Filenames::in(misc, I"resource.jsonr"); + case REGISTRY_JSON_REQS_IRES: + return Filenames::in(misc, I"registry.jsonr"); case CBLORB_REPORT_MODEL_IRES: return InstalledFiles::varied_by_platform(models, I"CblorbModel.html");