inweb-bootstrap/Chapter 4/Programming Languages.w
2019-02-04 22:26:45 +00:00

507 lines
19 KiB
OpenEdge ABL

[Languages::] Programming Languages.
To characterise the relevant differences in behaviour between the
various programming languages supported.
@h Languages.
The conventions for writing, weaving and tangling a web are really quite
independent of the programming language being written, woven or tangled;
Knuth began literate programming with Pascal, but now uses C, and the original
Pascal webs were mechanically translated into C ones with remarkably little
fuss or bother. Modern LP tools, such as |noweb|, aim to be language-agnostic.
But of course if you act the same on all languages, you give up the benefits
which might follow from knowing something about the languages you actually
write in.
The idea, then, is that Chapters 1 to 3 of the Inweb code treat all
material the same, and Chapter 4 contains all of the funny little exceptions
and special cases for particular programming languages. (This means Chapter 4
can't be understood without having at least browsed Chapters 1 to 3 first.)
Each language supported by Inweb has an instance of the following structure:
=
typedef struct programming_language {
text_stream *language_name;
text_stream *file_extension; /* by default output to a file whose name has this extension */
text_stream *source_file_extension; /* by default input from a file whose name has this extension */
int supports_enumerations; /* as it will, if it belongs to the C family of languages */
int supports_namespaces; /* really just for InC */
METHOD_CALLS
MEMORY_MANAGEMENT
} programming_language;
programming_language *default_language = NULL;
programming_language *Languages::default(void) { return default_language; }
programming_language *Languages::new_language(text_stream *name, text_stream *ext) {
programming_language *pl = CREATE(programming_language);
pl->language_name = Str::duplicate(name);
pl->file_extension = Str::duplicate(ext);
pl->supports_enumerations = FALSE;
pl->source_file_extension = I".w";
pl->methods = Methods::new_set();
pl->supports_namespaces = FALSE;
if (default_language == NULL) default_language = pl;
return pl;
}
@ =
programming_language *Languages::find_by_name(text_stream *lname) {
programming_language *pl;
LOOP_OVER(pl, programming_language)
if (Str::eq(lname, pl->language_name))
return pl;
Errors::fatal_with_text("unsupported programming language '%S'", lname);
return NULL;
}
@h Creation.
This must be performed very early in Inweb's run.
=
void Languages::create_programming_languages(void) {
CLike::create_C(); /* must be first, to make C the default language */
CLike::create_CPP();
InCSupport::create();
InformSupport::create_I6();
InformSupport::create_I7();
PerlSupport::create();
/* together with a featureless language: */
Languages::new_language(I"Plain Text", I".txt");
}
@h Parsing methods.
Really all of the functionality of languages is provided through method calls,
all of them made from this section. That means a lot of simple wrapper routines
which don't do very much. This section may still be useful to read, since it
documents what amounts to an API.
We begin with parsing extensions. When these are used, we have already read
the web into chapters, sections and paragraphs, but for some languages we will
need a more detailed picture.
|FURTHER_PARSING_PAR_MTID| is "further" in that it is called when the main
parser has finished work; it typically looks over the whole web for something
of interest.
@e FURTHER_PARSING_PAR_MTID
=
VMETHOD_TYPE(FURTHER_PARSING_PAR_MTID, programming_language *pl, web *W)
void Languages::further_parsing(web *W, programming_language *pl) {
VMETHOD_CALL(pl, FURTHER_PARSING_PAR_MTID, W);
}
@ |SUBCATEGORISE_LINE_PAR_MTID| looks at a single line, after the main parser
has given it a category. The idea is not so much to second-guess the parser
(although we can) but to change to a more exotic category which it would
otherwise never produce.
@e SUBCATEGORISE_LINE_PAR_MTID
=
VMETHOD_TYPE(SUBCATEGORISE_LINE_PAR_MTID, programming_language *pl, source_line *L)
void Languages::subcategorise_line(programming_language *pl, source_line *L) {
VMETHOD_CALL(pl, SUBCATEGORISE_LINE_PAR_MTID, L);
}
@ Comments have different syntax in different languages. The method here is
expected to look for a comment on the |line|, and if so to return |TRUE|,
but not before splicing the non-comment parts of the line before and
within the comment into the supplied strings.
@e PARSE_COMMENT_TAN_MTID
=
IMETHOD_TYPE(PARSE_COMMENT_TAN_MTID, programming_language *pl, text_stream *line, text_stream *before, text_stream *within)
int Languages::parse_comment(programming_language *pl,
text_stream *line, text_stream *before, text_stream *within) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, PARSE_COMMENT_TAN_MTID, line, before, within);
return rv;
}
@h Tangling methods.
We take these roughly in order of their effects on the tangled output, from
the top to the bottom of the file.
The top of the tangled file is a header called the "shebang". By default,
there's nothing there, but |SHEBANG_TAN_MTID| allows the language to add one.
For example, Perl prints |#!/usr/bin/perl| here.
@e SHEBANG_TAN_MTID
=
VMETHOD_TYPE(SHEBANG_TAN_MTID, programming_language *pl, text_stream *OUT, web *W, tangle_target *target)
void Languages::shebang(OUTPUT_STREAM, programming_language *pl, web *W, tangle_target *target) {
VMETHOD_CALL(pl, SHEBANG_TAN_MTID, OUT, W, target);
}
@ Next is the disclaimer, text warning the human reader that she is looking
at tangled (therefore not original) material.
@e SUPPRESS_DISCLAIMER_TAN_MTID
=
IMETHOD_TYPE(SUPPRESS_DISCLAIMER_TAN_MTID, programming_language *pl)
void Languages::disclaimer(text_stream *OUT, programming_language *pl, web *W, tangle_target *target) {
int rv = FALSE;
IMETHOD_CALLV(rv, pl, SUPPRESS_DISCLAIMER_TAN_MTID);
if (rv == FALSE)
Languages::comment(OUT, pl, I"Tangled output generated by inweb: do not edit");
}
@ Next is the disclaimer, text warning the human reader that she is looking
at tangled (therefore not original) material.
@e ADDITIONAL_EARLY_MATTER_TAN_MTID
=
VMETHOD_TYPE(ADDITIONAL_EARLY_MATTER_TAN_MTID, programming_language *pl, text_stream *OUT, web *W, tangle_target *target)
void Languages::additional_early_matter(text_stream *OUT, programming_language *pl, web *W, tangle_target *target) {
VMETHOD_CALL(pl, ADDITIONAL_EARLY_MATTER_TAN_MTID, OUT, W, target);
}
@ A tangled file then normally declares "definitions". The following write a
definition of the constant named |term| as the value given. If the value spans
multiple lines, the first-line part is supplied to |START_DEFN_TAN_MTID| and
then subsequent lines are fed in order to |PROLONG_DEFN_TAN_MTID|. At the end,
|END_DEFN_TAN_MTID| is called.
@e START_DEFN_TAN_MTID
@e PROLONG_DEFN_TAN_MTID
@e END_DEFN_TAN_MTID
=
IMETHOD_TYPE(START_DEFN_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *term, text_stream *start, section *S, source_line *L)
IMETHOD_TYPE(PROLONG_DEFN_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *more, section *S, source_line *L)
IMETHOD_TYPE(END_DEFN_TAN_MTID, programming_language *pl, text_stream *OUT, section *S, source_line *L)
void Languages::start_definition(OUTPUT_STREAM, programming_language *pl,
text_stream *term, text_stream *start, section *S, source_line *L) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, START_DEFN_TAN_MTID, OUT, term, start, S, L);
if (rv == FALSE)
Main::error_in_web(I"this programming language does not support @d", L);
}
void Languages::prolong_definition(OUTPUT_STREAM, programming_language *pl,
text_stream *more, section *S, source_line *L) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, PROLONG_DEFN_TAN_MTID, OUT, more, S, L);
if (rv == FALSE)
Main::error_in_web(I"this programming language does not support multiline @d", L);
}
void Languages::end_definition(OUTPUT_STREAM, programming_language *pl,
section *S, source_line *L) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, END_DEFN_TAN_MTID, OUT, S, L);
}
@ Then we have some "predeclarations"; for example, for C-like languages we
automatically predeclare all functions, obviating the need for header files.
@e ADDITIONAL_PREDECLARATIONS_TAN_MTID
=
IMETHOD_TYPE(ADDITIONAL_PREDECLARATIONS_TAN_MTID, programming_language *pl, text_stream *OUT, web *W)
void Languages::additional_predeclarations(OUTPUT_STREAM, programming_language *pl, web *W) {
VMETHOD_CALL(pl, ADDITIONAL_PREDECLARATIONS_TAN_MTID, OUT, W);
}
@ So much for the special material at the top of a tangle: now we're into
the more routine matter, tangling ordinary paragraphs into code.
Languages have the ability to suppress paragraph macro expansion:
@e SUPPRESS_EXPANSION_TAN_MTID
=
IMETHOD_TYPE(SUPPRESS_EXPANSION_TAN_MTID, programming_language *pl, text_stream *material)
int Languages::allow_expansion(programming_language *pl, text_stream *material) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, SUPPRESS_EXPANSION_TAN_MTID, material);
return (rv)?FALSE:TRUE;
}
@ Inweb supports very few "tangle commands", that is, instructions written
inside double squares |[[Thus]]|. These can be handled by attaching methods
as follows, which return |TRUE| if they recognised and acted on the command.
@e TANGLE_COMMAND_TAN_MTID
=
IMETHOD_TYPE(TANGLE_COMMAND_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *data)
int Languages::special_tangle_command(OUTPUT_STREAM, programming_language *pl, text_stream *data) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, TANGLE_COMMAND_TAN_MTID, OUT, data);
return rv;
}
@ The following methods make it possible for languages to tangle unorthodox
lines into code. Ordinarily, only |CODE_BODY_LCAT| lines are tangled, but
we can intervene to say that we want to tangle a different line; and if we
do so, we should then act on that basis.
@e WILL_TANGLE_EXTRA_LINE_TAN_MTID
@e TANGLE_EXTRA_LINE_TAN_MTID
=
IMETHOD_TYPE(WILL_TANGLE_EXTRA_LINE_TAN_MTID, programming_language *pl, source_line *L)
VMETHOD_TYPE(TANGLE_EXTRA_LINE_TAN_MTID, programming_language *pl, text_stream *OUT, source_line *L)
int Languages::will_insert_in_tangle(programming_language *pl, source_line *L) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, WILL_TANGLE_EXTRA_LINE_TAN_MTID, L);
return rv;
}
void Languages::insert_in_tangle(OUTPUT_STREAM, programming_language *pl, source_line *L) {
VMETHOD_CALL(pl, TANGLE_EXTRA_LINE_TAN_MTID, OUT, L);
}
@ In order for C compilers to report C syntax errors on the correct line,
despite rearranging by automatic tools, C conventionally recognises the
preprocessor directive |#line| to tell it that a contiguous extract follows
from the given file; we generate this automatically.
@e INSERT_LINE_MARKER_TAN_MTID
=
VMETHOD_TYPE(INSERT_LINE_MARKER_TAN_MTID, programming_language *pl, text_stream *OUT, source_line *L)
void Languages::insert_line_marker(OUTPUT_STREAM, programming_language *pl, source_line *L) {
VMETHOD_CALL(pl, INSERT_LINE_MARKER_TAN_MTID, OUT, L);
}
@ The following hooks are provided so that we can top and/or tail the expansion
of paragraph macros in the code. For example, C-like languages, use this to
splice |{| and |}| around the expanded matter.
@e BEFORE_MACRO_EXPANSION_TAN_MTID
@e AFTER_MACRO_EXPANSION_TAN_MTID
=
VMETHOD_TYPE(BEFORE_MACRO_EXPANSION_TAN_MTID, programming_language *pl, text_stream *OUT, para_macro *pmac)
VMETHOD_TYPE(AFTER_MACRO_EXPANSION_TAN_MTID, programming_language *pl, text_stream *OUT, para_macro *pmac)
void Languages::before_macro_expansion(OUTPUT_STREAM, programming_language *pl, para_macro *pmac) {
VMETHOD_CALL(pl, BEFORE_MACRO_EXPANSION_TAN_MTID, OUT, pmac);
}
void Languages::after_macro_expansion(OUTPUT_STREAM, programming_language *pl, para_macro *pmac) {
VMETHOD_CALL(pl, AFTER_MACRO_EXPANSION_TAN_MTID, OUT, pmac);
}
@ It's a sad necessity, but sometimes we have to unconditionally tangle code
for a preprocessor to conditionally read: that is, to tangle code which contains
|#ifdef| or similar preprocessor directive.
@e OPEN_IFDEF_TAN_MTID
@e CLOSE_IFDEF_TAN_MTID
=
VMETHOD_TYPE(OPEN_IFDEF_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *symbol, int sense)
VMETHOD_TYPE(CLOSE_IFDEF_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *symbol, int sense)
void Languages::open_ifdef(OUTPUT_STREAM, programming_language *pl, text_stream *symbol, int sense) {
VMETHOD_CALL(pl, OPEN_IFDEF_TAN_MTID, OUT, symbol, sense);
}
void Languages::close_ifdef(OUTPUT_STREAM, programming_language *pl, text_stream *symbol, int sense) {
VMETHOD_CALL(pl, CLOSE_IFDEF_TAN_MTID, OUT, symbol, sense);
}
@ Now a routine to tangle a comment. Languages without comment should write nothing.
@e COMMENT_TAN_MTID
=
VMETHOD_TYPE(COMMENT_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *comm)
void Languages::comment(OUTPUT_STREAM, programming_language *pl, text_stream *comm) {
VMETHOD_CALL(pl, COMMENT_TAN_MTID, OUT, comm);
}
@ The inner code tangler now acts on all code known not to contain CWEB
macros or double-square substitutions. In almost every language this simply
passes the code straight through, printing |original| to |OUT|.
@e TANGLE_CODE_UNUSUALLY_TAN_MTID
=
IMETHOD_TYPE(TANGLE_CODE_UNUSUALLY_TAN_MTID, programming_language *pl, text_stream *OUT, text_stream *original)
void Languages::tangle_code(OUTPUT_STREAM, programming_language *pl, text_stream *original) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, TANGLE_CODE_UNUSUALLY_TAN_MTID, OUT, original);
if (rv == FALSE) WRITE("%S", original);
}
@ We finally reach the bottom of the tangled file, a footer called the "gnabehs":
@e GNABEHS_TAN_MTID
=
VMETHOD_TYPE(GNABEHS_TAN_MTID, programming_language *pl, text_stream *OUT, web *W)
void Languages::gnabehs(OUTPUT_STREAM, programming_language *pl, web *W) {
VMETHOD_CALL(pl, GNABEHS_TAN_MTID, OUT, W);
}
@ But we still aren't quite done, because some languages need to produce
sidekick files alongside the main tangle file. This method exists to give
them the opportunity.
@e ADDITIONAL_TANGLING_TAN_MTID
=
VMETHOD_TYPE(ADDITIONAL_TANGLING_TAN_MTID, programming_language *pl, web *W, tangle_target *target)
void Languages::additional_tangling(programming_language *pl, web *W, tangle_target *target) {
VMETHOD_CALL(pl, ADDITIONAL_TANGLING_TAN_MTID, W, target);
}
@h Weaving methods.
This metnod shouldn't do any actual weaving: it should simply initialise
anything that the language in question might need later.
@e BEGIN_WEAVE_WEA_MTID
=
VMETHOD_TYPE(BEGIN_WEAVE_WEA_MTID, programming_language *pl, section *S, weave_target *wv)
void Languages::begin_weave(section *S, weave_target *wv) {
VMETHOD_CALL(S->sect_language, BEGIN_WEAVE_WEA_MTID, S, wv);
}
@ This method allows languages to tell the weaver to ignore certain lines.
@e SKIP_IN_WEAVING_WEA_MTID
=
IMETHOD_TYPE(SKIP_IN_WEAVING_WEA_MTID, programming_language *pl, weave_target *wv, source_line *L)
int Languages::skip_in_weaving(programming_language *pl, weave_target *wv, source_line *L) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, SKIP_IN_WEAVING_WEA_MTID, wv, L);
return rv;
}
@ Languages most do syntax colouring by having a "state" (this is now inside
a comment, inside qupted text, and so on); the following method is provided
to reset that state, if so. Inweb runs it once per paragraph for safety's
sake, which minimises the knock-on effect of any colouring mistakes.
@e RESET_SYNTAX_COLOURING_WEA_MTID
=
VMETHOD_TYPE(RESET_SYNTAX_COLOURING_WEA_MTID, programming_language *pl)
void Languages::reset_syntax_colouring(programming_language *pl) {
VMETHOD_CALLV(pl, RESET_SYNTAX_COLOURING_WEA_MTID);
}
@ And this is where colouring is done. The model here is that the code to
be coloured is in |matter|. A parallel text called |colouring| matches it
up, chatacter for character. For example, a language might colour like so:
|int x = 55;|
|rrrpipppnnp|
The initial state is |ppp...p|, everything "plain", unless the following
method does something to change that.
@e SYNTAX_COLOUR_WEA_MTID
@d MACRO_COLOUR 'm'
@d FUNCTION_COLOUR 'f'
@d RESERVED_COLOUR 'r'
@d ELEMENT_COLOUR 'e'
@d IDENTIFIER_COLOUR 'i'
@d CHAR_LITERAL_COLOUR 'c'
@d CONSTANT_COLOUR 'n'
@d STRING_COLOUR 's'
@d PLAIN_COLOUR 'p'
@d EXTRACT_COLOUR 'x'
=
IMETHOD_TYPE(SYNTAX_COLOUR_WEA_MTID, programming_language *pl, text_stream *OUT, weave_target *wv, web *W,
chapter *C, section *S, source_line *L, text_stream *matter, text_stream *colouring)
int Languages::syntax_colour(OUTPUT_STREAM, programming_language *pl, weave_target *wv,
web *W, chapter *C, section *S, source_line *L, text_stream *matter, text_stream *colouring) {
Str::copy(colouring, matter);
for (int i=0; i < Str::len(matter); i++) Str::put_at(colouring, i, PLAIN_COLOUR);
int rv = FALSE;
if (L->category != TEXT_EXTRACT_LCAT) {
IMETHOD_CALL(rv, pl, SYNTAX_COLOUR_WEA_MTID, OUT, wv, W, C, S, L, matter, colouring);
}
return rv;
}
@ This method is called for each code line to be woven. If it returns |FALSE|, the
weaver carries on in the normal way. If not, it does nothing, assuming that the
method has already woven something more attractive.
@e WEAVE_CODE_LINE_WEA_MTID
=
IMETHOD_TYPE(WEAVE_CODE_LINE_WEA_MTID, programming_language *pl, text_stream *OUT, weave_target *wv, web *W,
chapter *C, section *S, source_line *L, text_stream *matter, text_stream *concluding_comment)
int Languages::weave_code_line(OUTPUT_STREAM, programming_language *pl, weave_target *wv,
web *W, chapter *C, section *S, source_line *L, text_stream *matter, text_stream *concluding_comment) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, WEAVE_CODE_LINE_WEA_MTID, OUT, wv, W, C, S, L, matter, concluding_comment);
return rv;
}
@ When Inweb creates a new |^"Theme"|, it lets everybody know about that.
@e NOTIFY_NEW_TAG_WEA_MTID
=
VMETHOD_TYPE(NOTIFY_NEW_TAG_WEA_MTID, programming_language *pl, theme_tag *tag)
void Languages::new_tag_declared(theme_tag *tag) {
programming_language *pl;
LOOP_OVER(pl, programming_language)
VMETHOD_CALL(pl, NOTIFY_NEW_TAG_WEA_MTID, tag);
}
@h Analysis methods.
These are really a little miscellaneous, but they all have to do with looking
at the code in a web and working out what's going on, rather than producing
any weave or tangle output.
This one provides details to add to the section catalogue if |-structures|
or |-functions| is used at the command line:
@e CATALOGUE_ANA_MTID
=
VMETHOD_TYPE(CATALOGUE_ANA_MTID, programming_language *pl, section *S, int functions_too)
void Languages::catalogue(programming_language *pl, section *S, int functions_too) {
VMETHOD_CALL(pl, CATALOGUE_ANA_MTID, S, functions_too);
}
@ The "preweave analysis" is an opportunity to look through the code before
any weaving of it occurs. It's never called on a tangle run. These methods
are called first and last in the process, respectively. (What happens in
between is essentially that Inweb looks for identifiers, for later syntax
colouring purposes.)
@e EARLY_PREWEAVE_ANALYSIS_ANA_MTID
@e LATE_PREWEAVE_ANALYSIS_ANA_MTID
=
VMETHOD_TYPE(EARLY_PREWEAVE_ANALYSIS_ANA_MTID, programming_language *pl, web *W)
VMETHOD_TYPE(LATE_PREWEAVE_ANALYSIS_ANA_MTID, programming_language *pl, web *W)
void Languages::early_preweave_analysis(programming_language *pl, web *W) {
VMETHOD_CALL(pl, EARLY_PREWEAVE_ANALYSIS_ANA_MTID, W);
}
void Languages::late_preweave_analysis(programming_language *pl, web *W) {
VMETHOD_CALL(pl, LATE_PREWEAVE_ANALYSIS_ANA_MTID, W);
}
@ And finally: in InC only, a few structure element names are given very slightly
special treatment, and this method decides which.
@e SHARE_ELEMENT_ANA_MTID
=
IMETHOD_TYPE(SHARE_ELEMENT_ANA_MTID, programming_language *pl, text_stream *element_name)
int Languages::share_element(programming_language *pl, text_stream *element_name) {
int rv = FALSE;
IMETHOD_CALL(rv, pl, SHARE_ELEMENT_ANA_MTID, element_name);
return rv;
}