Prev:WEB 6 Top:WEB 0 Next:WEB 8
Jon Breuer - September 10, 2024.
New Features:
I used WEB4 to write documentation for my Joy interpreter, and I hit a few rough points I'd like to improve.
This is the description of some code.
This is the code
//And this is the body of that code.
I'd prefer the header first, then the description, then the code.
Of this wishlist, including web4's source is the priority. I'll have to hack my changes into it to get started.
In asdf, I'll insert a new handler for @# which will be my include/pragma/define tag. My extensions will lie under that.
Existing parse_web_then_tangle_and_weave
void parse_web_then_tangle_and_weave(...) { ... while(charIndex < fileContents.length) { dchar ch = fileContents[charIndex]; if(ch == '@') { if(chNext == '@') { // Escaped At } else if(chNext == 'p') { // Program Start } else if(chNext == '>') { // End Code Section } else if(chNext == '<') { // Start Code Section } else if(chNext == '*') { // Headers } else if (chNext == '#') { // A pragma section will instruct the parser. Process pragma section } else { // Anything else is documentation. } } else { // Anything else is documentation. } } ... }
I'm imagining the pragmas to look like @ #include @ > or @ #define new type @ >. Read the entire pragma command header, then determine what to do with it.
Process pragma section
Read pragma command Process include pragma
The existing slurp_section function is already built to read the whole section. I'll use that.
Read pragma command
SBlock[] pragmaBlocks = slurp_section(fileContents, charIndex, lineNumber, inputFilename, false, ESectionType.IDENTIFIER);
assert(pragmaBlocks.length == 1);
string command = pragmaBlocks[0].content;
Include is the important one.
Process include pragma
if(command.startsWith("include ")) { Extract filename from include command Read included file contents Parse included file Insert a comment in the documentation Insert code from included file. }
I'll treat the rest of the identifier as the filename. no quotes to avoid.
Extract filename from include command
string includeFilename = command[8..$];
This is the same command I use in main() to read the primary input file.
Read included file contents
string includeFileContents; try { includeFileContents = cast(string) std.file.read(includeFilename); } catch(Exception err) { writefln("Error: %s", err.msg); return []; }
Use the existing parse_web function.
Parse included file
string parsedIncludeDisplay= ""; string parsedIncludeCode= ""; SSection[] includedSections = parse_web( parsedIncludeDisplay, parsedIncludeCode, includeFileContents, includeFilename);
I'm torn, but I don't want all the discussion from the previous file. The writer should include a hyperlink back to the previous document, but since I'm parsing the source, I don't know what the published name will be.
Insert a comment in the documentation
fileSections ~= SSection("Included " ~ includeFilename, ESectionType.PARAGRAPH, [SBlock(ESectionType.PARAGRAPH, lineNumber, "Included " ~ includeFilename)]);
The whole point. Insert the code here.
Insert code from included file.
//writefln("%d sections parsed from %s", includedSections.length, includeFilename); //writefln("%s", includedSections[0..15]); //SSection[] filteredSections = //std.array.array(includedSections.filter!(section=>section.type == ESectionType.CODE)); //writefln("%d filtered sections.", includedSections.length); //fileSections ~= filteredSections; auto filtered = includedSections.filter!(section=>section.type == ESectionType.CODE); foreach(SSection section; filtered) { fileSections ~= section; }
Skip code included from literate_programming_4_source
__main__
File Header Library Imports Definitions Utility Functions Main Function Comments to test appending and replacing
File Header
//////////// // WEB4.D // // This is a level 4 bootstrapping Literate Programming thing. // It will insert indices and tables of contents. It may also allow appending or replacing sections. // module web3;
Library Imports
private import std.algorithm; // Needed for countUntil and searching private import std.ascii; // Character type checks. private import std.file; // Needed for file input and output private import std.stdio; // Needed for error reporting and my debugging private import std.string; // These programs are all about string processing.
Definitions
enum ESectionType { CODE, HEADER, PARAGRAPH, IDENTIFIER, INDEX_TERM, Section Type Formats PRE, // Terms? Literal/Emphasis? BOLD, }; struct SSection { string name; ESectionType type; SBlock[] contents; }; struct SBlock { ESectionType type; int lineNumber; string content; Custom Type Format }; New character definitions
Section Type Formats
PRE, // Terms? Literal/Emphasis?
BOLD,
Utility Functions
An enhanced version of countUntil that can start at a given string position. Format code for display with colors and escaped HTML codes. Escape special HTML Characters with their safe entities. Parse an entire section of text, recursing for definitions if needed. Find Matching Identifiers expand_code_identifier parse_web_then_tangle_and_weave
An enhanced version of countUntil that can start at a given string position.
ptrdiff_t countFromPosUntil(string haystack, ptrdiff_t startIndex, string needle) { ptrdiff_t offset = countUntil(haystack[startIndex..haystack.length], needle); if(offset < 0) { return offset; } return startIndex + offset; }
Format code for display with colors and escaped HTML codes.
string formatCodeForDisplay(string source, int lineNumber) { string output = ""; string scanner = escapeHTMLCharacters(source); scanner: while(!scanner.empty) { if(scanner.startsWith("//")) { // Color comments. output ~= "<span class=\"code_comment\">"; int lineLength = countUntil(scanner, "\n"); if(lineLength < 0) { lineLength = scanner.length; } output ~= scanner[0..lineLength - 1]; output ~= "</span>"; scanner = scanner[lineLength..scanner.length]; } else if(scanner.startsWith("\"") || scanner.startsWith("\'")) { // Color strings. char stringType = scanner[0]; output ~= "<span class=\"code_string\">"; int stringLength = 1; Existing string formatting code output ~= scanner[0..min(scanner.length, stringLength + 1)]; output ~= "</span>"; scanner = scanner[min(scanner.length, stringLength + 1)..scanner.length]; } else { if(isAlpha(scanner[0])) { bool isNotIdentifier(dchar ch) { return !(isAlpha(ch) || isDigit(ch) || ch == '_'); } int wordLength = countUntil!isNotIdentifier(scanner); if(wordLength < 0) { wordLength = scanner.length; } const string[] identifiers = [ "const", "bool", "break", "char", "dchar", "else", "for", "float", "if", "import", "int", "main", "module", "private", "return", "string", "std", "void", "while", ]; if(wordLength > 0 && !findAmong(identifiers, [scanner[0..wordLength]]).empty) { // Special identifiers output ~= "<span class=\"code_identifier\">"; output ~= scanner[0..wordLength]; output ~= "</span>"; } else { output ~= scanner[0..wordLength]; } scanner = scanner[wordLength..scanner.length]; } else { output ~= scanner[0]; scanner = scanner[1..scanner.length]; } } } return output; }
Escape special HTML Characters with their safe entities.
string escapeHTMLCharacters(string source) { string output; string scanner = source; foreach(dchar ch; source) { if(countUntil("<>&", ch) >= 0) { if(ch == '<') { output ~= "<"; } else if(ch == '>') { output ~= ">"; } else if(ch == '&') { output ~= "&"; } else { writefln("BUG: Only partly implemented support for '%s'.", ch); } } else { output ~= ch; } } return output; }
Parse an entire section of text, recursing for definitions if needed.
SBlock[] slurp_section(string contents, ref int offset, ref int lineNumber, string inputFilename, bool recurse, ESectionType sectionType) { SBlock[] results; string currentBlock = ""; int startLineNumber = lineNumber; int index = offset; for(; index < contents.length; index++) { if(contents[index] == '@') { if(recurse && contents[index + 1] == '<') { results ~= SBlock(sectionType, startLineNumber, currentBlock); currentBlock = ""; startLineNumber = lineNumber; slurp identifier subsection if(contents[index..$].startsWith("@>")) { index += 1; // Bug Fix: This is a for loop, we only need to skip one character extra. } else { writefln("Identifier '%s' invoked without close tag. at %s", identifier, contents[index..min($, index + 10)]); break; } } else if(contents[index + 1] == '@') { currentBlock ~= contents[index]; // Skip the escaped at symbol. index++; // Web7 improvement. slurp_section warn about stray HTML tags slurp styled section } else { break; } } Handle embedded index points else { if(contents[index] == '\n') { lineNumber++; } currentBlock ~= contents[index]; } } results ~= SBlock(ESectionType.CODE, startLineNumber, currentBlock); offset = index; return results; }
expand_code_identifier
string expand_code_identifier(SSection[] sections, string identifier, string inputFilename) { string output; output ~= "/* from "~identifier~" */"; SSection[] definitions = find_matching_identifiers(sections, identifier); if(definitions.empty) { writefln("ERROR: Unable to find identifier '%s'.", identifier); return format("ERROR: %s is undefined", identifier); } foreach(section; definitions) { foreach(block; section.contents) { output ~= format("\n#line %d \"%s\"\n", block.lineNumber, inputFilename); if(block.type == ESectionType.IDENTIFIER) { output ~= expand_code_identifier(sections, block.content, inputFilename); } else { output ~= block.content; } } } return output; }
parse_web_then_tangle_and_weave
SSection[] parse_web(ref string outputDisplayContents, ref string outputCodeContents, string fileContents, string inputFilename) { SSection[] fileSections; int lineNumber = 0; int charIndex = 0; while(charIndex < fileContents.length) { dchar ch = fileContents[charIndex]; if(ch == '@') { dchar chNext = charIndex < fileContents.length - 1 ? fileContents[charIndex + 1] : 0; charIndex += 2; if(chNext == '@') { // It's just an escaped at. Continue parsing. // Web7 improvement. parse_web warn about stray HTML tags } else if(chNext == 'p') { fileSections ~= SSection("__main__", ESectionType.CODE, slurp_section(fileContents, charIndex, lineNumber, inputFilename, true, ESectionType.CODE)); } else if(chNext == '>') { //End tag. This should be the end of this block. } else if(chNext == '<') { SBlock[] identifierBlocks = slurp_section(fileContents, charIndex, lineNumber, inputFilename, false, ESectionType.IDENTIFIER); assert(identifierBlocks.length == 1); string identifier = identifierBlocks[0].content; SBlock[] sectionContents; if(fileContents[charIndex..charIndex + 3] == "@>=") { charIndex += 3; sectionContents = slurp_section(fileContents, charIndex, lineNumber, inputFilename, true, ESectionType.CODE); } else { writefln("Identifier '%s' invoked outside program and not a definition.", identifier); } fileSections ~= SSection(identifier, ESectionType.CODE, sectionContents); } else if(chNext == '*') { int titleEndingPeriod = countFromPosUntil(fileContents, charIndex, "."); string title = ""; if(titleEndingPeriod > 0) { title = fileContents[charIndex..titleEndingPeriod]; charIndex = titleEndingPeriod + 1; } fileSections ~= SSection(title, ESectionType.HEADER, slurp_section(fileContents, charIndex, lineNumber, inputFilename, false, ESectionType.HEADER)); } else if (chNext == '#') { // A pragma section will instruct the parser. // This was added for WEB7. Process pragma section } else { // '@ ' will be converted into a section. fileSections ~= SSection("", ESectionType.PARAGRAPH, SBlock(ESectionType.PARAGRAPH, lineNumber, "<p>") ~ slurp_section(fileContents, charIndex, lineNumber, inputFilename, false, ESectionType.PARAGRAPH)); } } else { fileSections ~= SSection("", ESectionType.PARAGRAPH, slurp_section(fileContents, charIndex, lineNumber, inputFilename, false, ESectionType.PARAGRAPH)); } } return fileSections; } void parse_web_then_tangle_and_weave(ref string outputDisplayContents, ref string outputCodeContents, string fileContents, string inputFilename) { SSection[] fileSections = parse_web(outputDisplayContents, outputCodeContents, fileContents, inputFilename); New Display Work in parse_web_then_tangle_and_weave New support for main sections foreach(block; mainSection[0].contents) { if(block.type == ESectionType.IDENTIFIER) { outputCodeContents ~= expand_code_identifier(fileSections, block.content, inputFilename); } else { outputCodeContents ~= block.content; } } }
Main Function
void main(string[] args) { if(args.length != 4) { writefln("Usage: WEB3 inputFile outputHTMLFile outputCodeFile"); Missing Main Arguments Return } const string inputFilename = args[1]; Main function read input file if(fileContents.length == 0) { writefln("Unable to read file '%s'.", inputFilename); return; } Initialize custom formats string outputDisplayContents = ""; string outputCodeContents = ""; parse_web_then_tangle_and_weave( outputDisplayContents, outputCodeContents, fileContents, inputFilename); string outputDisplayFilename = args[2]; Main function write output display file string outputCodeFilename = args[3]; Main function write output code file }
New Display Work in parse_web_then_tangle_and_weave
foreach(SSection section; fileSections) { insertTableOfContents insertIndex Better Headers if(section.type != ESectionType.CODE) { string paragraphReducer(string output, SBlock block) { if(block.type == ESectionType.INDEX_TERM) { return output ~ "<b id='"~section.name~block.content~"'><i>" ~ block.content.strip("|") ~ "</i></b>"; style display text } else { return output ~ block.content; } } string content = reduce!paragraphReducer("", section.contents); outputDisplayContents ~= content; } else { Better Code Blocks foreach(block; section.contents) { string outputContent = formatCodeForDisplay(block.content, block.lineNumber); if(block.type == ESectionType.IDENTIFIER) { outputDisplayContents ~= "<b><i>" ~ outputContent ~ "</i></b>"; } else { outputDisplayContents ~= outputContent; } } outputDisplayContents = outputDisplayContents.stripRight() ~ "</pre>"; } }
style display text
} else if(block.type == ESectionType.BOLD) { return output ~ "<b>" ~ block.content ~ "</b>"; } else if(block.type == ESectionType.PRE) { return output ~ "<i>" ~ block.content ~ "</i>";
Better Headers
if(section.type == ESectionType.HEADER) { outputDisplayContents ~= "<h3 id=\"" ~ section.name ~ "\">" ~ section.name ~"</h3>" ~ "<p>"; } else if(section.type == ESectionType.CODE) { outputDisplayContents ~= "<p id=\"" ~ section.name ~ "\"><b>" ~ section.name ~"</b>"; }
Better Code Blocks
static if(false) { outputDisplayContents ~= format(" <button onclick=\"toggle_element_hidden('%s_code')\">Show/Hide Code</button>", section.name); } outputDisplayContents ~= "<pre id=\"" ~ section.name ~ "_code" ~ "\">";
slurp identifier subsection
slurp: read identifier
slurp: check for new definition
slurp: insert subsection
slurp: read identifier
int preIdentifierIndex = index; index += 2; SBlock[] identifierBlocks = slurp_section(contents, index, lineNumber, inputFilename, false, sectionType); assert(identifierBlocks.length == 1); string identifier = identifierBlocks[0].content;
slurp: check for new definition
if(contents[index..$].startsWith("@>=")) { // The end of one block has bumped into the start of another. Roll back. index = preIdentifierIndex; break; }
slurp: insert subsection
// Now that we're sure this is a reference to an identifier and not a definition of a new identifier, continue.
results ~= SBlock(ESectionType.IDENTIFIER, lineNumber, identifier);
insertTableOfContents
if(section.name == "__table_of_contents__") { outputDisplayContents ~= "<h3>Table of Contents:</h3>"; outputDisplayContents ~= "<ul><ul>"; foreach(SSection tocSection; fileSections) { if(tocSection.name.startsWith("__")) { continue; } if(tocSection.type == ESectionType.HEADER) { outputDisplayContents ~= "</ul><li><b><a href=\"#"~tocSection.name~"\">" ~ tocSection.name ~"</a></b><ul>"; } else if(tocSection.type == ESectionType.CODE) { outputDisplayContents ~= "<li><a href=\"#"~tocSection.name~"\">" ~ tocSection.name ~"</a>"; } } outputDisplayContents ~= "</ul></ul>"; continue; }
insertIndex
if(section.name == "__index__") { Print the index header Scan sections for index targets Sort index alphabetically Print index entries outputDisplayContents ~= "</ul>"; continue; }
Print the index header
outputDisplayContents ~= "<h3>Index:</h3>"; outputDisplayContents ~= "<ul>";
Scan sections for index targets
string[] references; foreach(SSection indexSection; fileSections) { if(indexSection.name.startsWith("__")) { continue; } if(indexSection.type == ESectionType.HEADER || indexSection.type == ESectionType.CODE) { references ~= indexSection.name~"@"~indexSection.name; } foreach(SBlock block; indexSection.contents) { if(block.type == ESectionType.IDENTIFIER ) { references ~= block.content ~"@"~indexSection.name; } if(block.type == ESectionType.INDEX_TERM) { references ~= block.content.strip("|") ~"@"~indexSection.name~block.content; } } }
Sort index alphabetically
import std.algorithm.mutation : SwapStrategy; auto sortedReferences = sort!("a.toUpper < b.toUpper", SwapStrategy.stable) (references);
Print index entries
string lastReference = ""; int referenceCount = 0; foreach(string reference; sortedReferences) { Print a title for each index entry Count the links to each usage }
Print a title for each index entry
string[] components = reference.split("@"); if(components[0] != lastReference) { lastReference = components[0]; outputDisplayContents ~= "<li><b>" ~ components[0] ~ ":" ~ "</b>"; referenceCount = 0; } outputDisplayContents ~= " <a href=\"#" ~ components[1] ~ "\">" ;
Count the links to each usage
referenceCount++; if(components[0] == components[1]) { outputDisplayContents ~= "(definition) "~ "</a>"; } else { string indexNumber = format( "%d", referenceCount); outputDisplayContents ~= indexNumber ~ "</a>"; }
New support for main sections
SSection[] mainSection = find_matching_identifiers(fileSections, "__main__"); if(mainSection.length != 1) { writefln("ERROR: Exactly 1 __main__ section needed. %d found.", mainSection.length); foreach(section; mainSection) { writefln("%s found at line %d", section.name, section.contents[0].lineNumber); } return; } if(mainSection[0].type != ESectionType.CODE) { writefln("ERROR: __main__ section needs to be code."); return; }
Find Matching Identifiers
SSection[] find_matching_identifiers(SSection[] sections, string identifier) { SSection[] results; foreach(section; sections) { // A section might be name... or name...! or name...+. Check each form. if(section.name.endsWith("...")) { if(!identifier.startsWith(section.name[0..$-3])) { continue; } } else if(section.name.endsWith("...!") || section.name.endsWith("...+")) { if(!identifier.startsWith(section.name[0..$-4])) { continue; } } else if(section.name != identifier) { continue; } // We've found a name that matches. if(section.name.endsWith("...+")) { // Append tag. } else if(section.name.endsWith("...!")) { // Replace tag. results = []; } else if(!results.empty) { writefln("WARNING: Multiple matches for '%s' found. Use ...+ to append or ...! to replace.", identifier); } results ~= section; } return results; }
Comments to test appending and replacing
// There should be 2 appended comments, 1 replaced comment, and 1 partial comment. This message will repeat. Test appending Test replacing // There should be 2 appended comments, 1 replaced comment, and 1 partial comment. This message is the repetition.
Test appending
// This is the original named appending
Test appending...+
// This is the first appended comment
Test appending...+
// This is the second appended comment
Test replacing
// This is the wrong replaced comment
Test replacing...!
// This is an unseen replaced comment
Test replacing...!
// This is the correct replaced comment
Test partial names
Test partial ...
// Partial names appended
New character definitions
const dchar CHAR_PIPE = '|'; const dchar CHAR_AT = '@'; const dchar CHAR_NEWLINE = '\n';
Handle embedded index points
else if(contents[index] == CHAR_PIPE && sectionType != ESectionType.CODE) { if(contents[index + 1] == CHAR_PIPE) { currentBlock ~= contents[index]; // Skip the escaped pipe symbol. index++; } else { // Save the previous block. results ~= SBlock(sectionType, startLineNumber, currentBlock); currentBlock = ""; startLineNumber = lineNumber; currentBlock ~= contents[index]; for(index++; index < contents.length; index++) { currentBlock ~= contents[index]; if(contents[index] == CHAR_NEWLINE) { lineNumber++; } if(contents[index] == CHAR_PIPE) { if(index < contents.length - 1 && contents[index + 1] == CHAR_PIPE) { // Skip the escaped pipe symbol. index++; } else { break; } } else if(contents[index] == CHAR_AT) { if(index < contents.length - 1 && contents[index + 1] == CHAR_AT) { // Skip the escaped at symbol. index++; } else { writefln("WARNING: At symbol encountered before end of piped index term."); break; } } } if(index >= contents.length || contents[index] != CHAR_PIPE) { writefln("WARNING: Close pipe expected near line %d.", startLineNumber); break; } // Save the indexed identifier. results ~= SBlock(ESectionType.INDEX_TERM, startLineNumber, currentBlock); currentBlock = ""; startLineNumber = lineNumber; } }
slurp styled section
Detect a new text style Save off the previous block of text parse the styled section skip the close tag around the style section insert the style block into the section
Detect a new text style
} else if(contents[index + 1] == '^' || contents[index + 1] == '.') { ESectionType styleType = contents[index + 1] == '^' ? ESectionType.BOLD : ESectionType.PRE;
parse the styled section
SBlock[] textBlocks = slurp_section(contents, index, lineNumber, inputFilename, false, sectionType); if(!(textBlocks.length == 1)) { writefln("Error: One section expected in formatting near %d.", lineNumber); } assert(textBlocks.length == 1); string textBlock = textBlocks[0].content;
skip the close tag around the style section
if(contents[index..$].startsWith("@>")) { index += 1; } else { writefln("Identifier '%s' invoked without close tag. at %s", textBlock, contents[index..min($, index + 10)]); break; }
insert the style block into the section
results ~= SBlock(styleType, lineNumber, textBlock);
Save off the previous block of text
results ~= SBlock(sectionType, startLineNumber, currentBlock);
currentBlock = "";
startLineNumber = lineNumber;
index += 2;
Example broken string
// Note: This will fail in WEB3 or WEB4a writefln("This string starts but doesn't close. //"//The warning was getting to me, so I've silenced it here.
Existing string formatting code
while(stringLength < scanner.length && scanner[stringLength] != stringType) { if(scanner[stringLength] == '\\') { stringLength += 1; } stringLength += 1; } if(stringLength >= scanner.length) { writefln("ERROR: Unable to find close quote for string %s near line %d in string %s\n", scanner[0..min(scanner.length, 20)], lineNumber, source); break scanner; }
Existing string formatting code...!
while(stringLength < scanner.length && scanner[stringLength] != stringType && scanner[stringLength] != '\n') { if(scanner[stringLength] == '\\') { stringLength += 1; } stringLength += 1; } if(stringLength >= scanner.length || scanner[stringLength] != stringType) { writefln("WARNING: Unable to find close quote for string %s near line %d in string %s\n", scanner[0..min(scanner.length, 20)], lineNumber, source); // Not a fatal error, so we'll continue. // break scanner; }
Missing Main Arguments Return
return;
Department of Redundancy Department
// This is an infinite loop.
Department of Redundancy Department
xMain Function...+
Department of Redundancy Department
expand_code_identifier...!
string expand_code_identifier(SSection[] sections, string identifier, string inputFilename) { writefln("expand_code_identifier"); string output; output ~= "/* from "~identifier~" */"; SSection[] definitions = find_matching_identifiers(sections, identifier); if(definitions.empty) { writefln("ERROR: Unable to find identifier '%s'.", identifier); return format("ERROR: %s is undefined", identifier); } foreach(section; definitions) { foreach(block; section.contents) { output ~= format("\n#line %d \"%s\"\n", block.lineNumber, inputFilename); if(block.type == ESectionType.IDENTIFIER) { output ~= expand_code_identifier(sections, block.content, inputFilename); } else { output ~= block.content; } } } return output; }
expand_code_identifier...!
string expand_code_identifier(SSection[] sections, string identifier, string inputFilename, uint depth = 0) { if(depth > 100) { writefln("ERROR: identifier recursive reference depth exceeded in %s.", identifier); if(depth > 103) { // Allow a few extra references to get some error context. return format("ERROR: identifier recursive reference depth exceeded in %s.", identifier); } } string output; Output source code origin comment SSection[] definitions = find_matching_identifiers(sections, identifier); if(definitions.empty) { writefln("ERROR: Unable to find identifier '%s'.", identifier); return format("ERROR: %s is undefined", identifier); } foreach(section; definitions) { foreach(block; section.contents) { Output source code line pragma if(block.type == ESectionType.IDENTIFIER) { output ~= expand_code_identifier(sections, block.content, inputFilename, depth + 1); } else { output ~= block.content; } } } return output; }
Main function read input file
string fileContents = cast(string) std.file.read(inputFilename);
Main function read input file...!
string fileContents; try { fileContents = cast(string) std.file.read(inputFilename); } catch(Exception err) { writefln("Error: %s", err.msg); return; }
Main function write output display file
std.file.write(outputDisplayFilename, outputDisplayContents);
Main function write output code file
std.file.write(outputCodeFilename, outputCodeContents);
Main function write output display file...!
try { std.file.write(outputDisplayFilename, outputDisplayContents); } catch(Exception err) { writefln("Error: %s", err.msg); return; }
Main function write output code file...!
try { std.file.write(outputCodeFilename, outputCodeContents); } catch(Exception err) { writefln("Error: %s", err.msg); return; }
Modified parse_web_then_tangle_and_weave
outputDisplayContents = outputDisplayContents.stripRight() ~ "</pre>";
Output source code origin comment
output ~= "/* from "~identifier~" */";
Output source code line pragma
output ~= format("\n#line %d \"%s\"\n", block.lineNumber, inputFilename);
Output source code origin com...!
if(OutputSourceContextInformation) { output ~= "/* from "~identifier~" */"; }
Output source code line prag...!
if(OutputSourceContextInformation) { output ~= format("\n#line %d \"%s\"\n", block.lineNumber, inputFilename); }
Definitions...+
bool OutputSourceContextInformation = true;
Inline fix for sections eating next character
SBlock[] slurp_section(...) { ... for(; index < contents.length; index++) { if(contents[index..$].startsWith("@>")) { index += 2; // Bug Fix: This is a for loop, we only need to skip one character extra.
Library Imports...+
import std.array;
Comments to test ...+
// This is new code for Web7.
Huh. I printed the internal representation and the types aren't what I expect.
SSection("", PARAGRAPH, [ SBlock(PARAGRAPH, 18, ""), SBlock(CODE, 16, "I really want to redefine existing sections and dynamically add content into group sections.\r\n\r\n")])
Did some debugging. The problem isn't in the parser continuing to read the second at as a command, but in leaving stray angle brackets <content> in an HTML file. Because while my code is escaped, my content isn't. Hmm. This is a deeper design issue. I want @<Token@> to render and <b> to bold.
The program is doing the right thing. Can I add a warning message that I might be doing the wrong thing? @@<tag> won't render correctly because HTML thinks it's a tag. < hello there>
Existing parse_web
SSection[] parse_web(ref string outputDisplayContents, ref string outputCodeContents, string fileContents, string inputFilename) { ... while(charIndex < fileContents.length) { dchar ch = fileContents[charIndex]; if(ch == '@') { dchar chNext = charIndex < fileContents.length - 1 ? fileContents[charIndex + 1] : 0; if(chNext == '@') { // It's just an escaped at. Continue parsing.
Let's insert an extra check for raw less than that may be interpreted as HTML tags.
if(charIndex < fileContents.length - 1 && fileContents[charIndex] == '<' && fileContents[charIndex + 1] != ' ') { // I think the writer wrote at, at, lessthan to try and escape the at, but the lessthan will be problematic in HTML. writefln("Warning: Using %s%stag in an HTML file may have unintended effects. Near line %d in %s.", CHAR_AT, '<', lineNumber, inputFilename); }
And we need the same fix inside slurp_section.
if(index < contents.length - 1 && contents[index+1] == '<' && contents[index + 2] != ' ') { // I think the writer wrote at, at, lessthan to try and escape the at, but the lessthan will be problematic in HTML. writefln("Warning: Using %s%stag in an HTML file may have unintended effects. Near line %d in %s.", CHAR_AT, '<', lineNumber, inputFilename); }
Let's test this : @<an error@>? - It works.
Warning: Using @<tag in an HTML file may have unintended effects. Near line 207
This implementation can do bold text, italic text, and bold italic terms. I need an inline code style and maybe another. I want to define a style and be able to apply it.
@#define A=<b><i>content</i></b> ... @ This is a paragraph with @Asome styling@>.
would yield : This is a paragraph with some styling.
In New Display Work in parse_web_then_tangle_and_weave is this:
Existing code
foreach(SSection section; fileSections) { if(section.type != ESectionType.CODE) { string paragraphReducer(string output, SBlock block) { if(block.type == ESectionType.INDEX_TERM) { return output ~ "<b id='"~section.name~block.content~"'><i>" ~ block.content.strip("|") ~ "</i></b>"; } else if(block.type == ESectionType.BOLD) { return output ~ "<b>" ~ block.content ~ "</b>"; } else if(block.type == ESectionType.PRE) { return output ~ "<i>" ~ block.content ~ "</i>";
I'll hack WEB4 so I can improve those tags.
Before BOLD and PRE were separate types, I'm going to fold them into just CUSTOM.
Section Type Formats...!
CUSTOM,
The SBlock type now can have the type be CUSTOM and the exact formatter specified here.
Custom Type Format
string customFormat;
SFormat is a new type which will define the tags.
Definitions...+
struct SFormat{ string pre; string post; }; Global Variables
Global Variables
SFormat[string] Custom_formats;
To preserve backward compatibility, bold and italic are defined as custom formatters.
Initialize custom formats
Custom_formats["^"]=SFormat("<b>", "</b>"); Custom_formats["."]=SFormat("<i>", "</i>");
The existing Detect a new text style has hard-coded values:
Existing code
} else if(contents[index + 1] == '^' || contents[index + 1] == '.') { ESectionType styleType = contents[index + 1] == '^' ? ESectionType.BOLD : ESectionType.PRE;
and our new version will detect a registered formatter and use that.
Detect a new text style...!
} else if("" ~ contents[index + 1] in Custom_formats) { ESectionType styleType = ESectionType.CUSTOM; string customFormat = "" ~ contents[index + 1];
And note the format on the block of text.
insert the style block into the section...!
results ~= SBlock(styleType, lineNumber, textBlock, customFormat);
When styling the text, look up the formatter and apply it.
style display text...!
} else if(block.type == ESectionType.CUSTOM && block.customFormat in Custom_formats) { SFormat format = Custom_formats[block.customFormat]; return output ~ format.pre ~ block.content ~ format.post;
Let's check that the existing formats are working and hint that the future one works too.
Let's implement define.
Process pragma section...+
Process define pragma
Process define pragma
else if(command.startsWith("define ")) { split define into identifier and format split define format into pre and post register new define format }
split define into identifier and format
string defineExpression = command[7..$]; auto splitOnEqual = defineExpression.findSplit("="); string defineIdentifier = splitOnEqual[0];
split define format into pre and post
auto splitAroundContent = splitOnEqual[2].findSplit("..."); string pre = splitAroundContent[0]; string post = splitAroundContent[2];
register new define format
SFormat format = {pre, post}; Custom_formats[defineIdentifier] = format;
Hooray! I can now define a code style and short code snippets or identifiers will be formatted! Does this work? formatting with @<example WEB tags@> in them. Yes!