Syntax highlighting, refactor of link resolution.

* Added syntax highlighting using SyntaxHighlighter v3.0.83
  (see https://github.com/alexgorbatchev/SyntaxHighlighter)
* Modified Directive to have a link to the DocBlock that contains it. Modified
  JLPPegParser to account for this change.
* Modified LinkAnchor to include the ASTNode defining it. Also added
  LinkAnchorType enum to facilitate different types of links.
* LiterateMarkdownGenerator is now escaping HTML characters in the code sections
  and other places it is appropriate.
* Refactored the link resolution process. Added a new method
  `Processor.resolveLink(String, TargetDoc)` that now resolves a URL or URL
  fragment relative to the current output context. This function also takes over
  the job of resolving JLP links to link anchors and generating the correct URL.
  LiterateMarkdownGenerator now calls this instead of doing all the work itself.
* To support the syntax highlighter, the JarUtil class from com.jdbernard.util
  is included and the syntax highlighter resources are extracted into the output
  directory from a resource jar stored in the JLP main jar.
* The CSS and other assets for the output are no longer copied into every
  output directory, but now stored at the output root and linked correctly into
  the output files.
* Added references to the source file and file type for TargetDocs instead of
  computing them on the fly during processing.
This commit is contained in:
Jonathan Bernard 2012-01-02 20:29:36 -06:00
parent aac5915df7
commit 5028d38e35
18 changed files with 250 additions and 203 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,7 @@
#Thu, 29 Dec 2011 15:43:59 -0600
#Mon, 02 Jan 2012 20:27:05 -0600
name=jlp
version=1.3a
build.number=0
version=1.3
build.number=1
lib.local=true
release.dir=release
main.class=com.jdblabs.jlp.JLPMain

View File

@ -0,0 +1,65 @@
body {
font-family: 'Palatine Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
font-size: 15px;
line-height: 22px;
color: #252519;
margin: 0;
padding 0; }
a { color: #261a3b; }
a:visited { color: #261a3b; }
p{ margin: 0 0 15px 0; }
h1, h2, h3, h4, h5, h6 { margin: 0 0 15px 0; }
h1 { margin-top: 40px; }
dt { font-weight: bold; }
ul {
margin: 0;
padding-top: 0; }
#container { position: relative; }
table td {
border: 0;
outline: 0; }
td.docs, th.docs {
max-width: 600px;
min-width: 450px;
min-height: 5pc;
padding: 10px 25px 1px 50px;
overflow-x: hidden;
vertical-align: top;
text-align: left; }
.docs pre {
background: #f8f8ff;
border: 1px solid #dedede;
margin: 15px 0 15px;
padding-left: 15px; }
.docs tt, .docs code {
background: #f8f8ff;
border: 1px solid #dedede;
font-size: 12px;
padding: 0 0.2em; }
.docs table {
border: thin solid #dedede;
margin-left: 60px; }
td.code, th.code {
padding: 14px 15px 16px 25px;
width: 100%;
vertical-align: top;
border-left: 1px solid #e5e5ee; }
pre, tt, code {
font-size: 12px;
line-height: 18px;
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
margin: 0; padding: 0; }

View File

@ -1,132 +0,0 @@
body {
font-family: 'Palatine Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
font-size: 15px;
line-height: 22px;
color: #252519;
margin: 0;
padding 0; }
a { color: #261a3b; }
a:visited { color: #261a3b; }
p{ margin: 0 0 15px 0; }
h1, h2, h3, h4, h5, h6 { margin: 0 0 15px 0; }
h1 { margin-top: 40px; }
dt { font-weight: bold; }
ul {
margin: 0;
padding-top: 0; }
#container { position: relative; }
table td {
border: 0;
outline: 0; }
td.docs, th.docs {
max-width: 600px;
min-width: 450px;
min-height: 5pc;
padding: 10px 25px 1px 50px;
overflow-x: hidden;
vertical-align: top;
text-align: left; }
.docs pre {
background: #f8f8ff;
border: 1px solid #dedede;
margin: 15px 0 15px;
padding-left: 15px; }
.docs tt, .docs code {
background: #f8f8ff;
border: 1px solid #dedede;
font-size: 12px;
padding: 0 0.2em; }
.docs table {
border: thin solid #dedede;
margin-left: 60px; }
td.code, th.code {
padding: 14px 15px 16px 25px;
width: 100%;
vertical-align: top;
background: #f5f5ff;
border-left: 1px solid #e5e5ee; }
pre, tt, code {
font-size: 12px;
line-height: 18px;
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
margin: 0; padding: 0; }
/*---------------------- Syntax Highlighting -----------------------------*/
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
body .hll { background-color: #ffffcc }
body .c { color: #408080; font-style: italic } /* Comment */
body .err { border: 1px solid #FF0000 } /* Error */
body .k { color: #954121 } /* Keyword */
body .o { color: #666666 } /* Operator */
body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
body .cp { color: #BC7A00 } /* Comment.Preproc */
body .c1 { color: #408080; font-style: italic } /* Comment.Single */
body .cs { color: #408080; font-style: italic } /* Comment.Special */
body .gd { color: #A00000 } /* Generic.Deleted */
body .ge { font-style: italic } /* Generic.Emph */
body .gr { color: #FF0000 } /* Generic.Error */
body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
body .gi { color: #00A000 } /* Generic.Inserted */
body .go { color: #808080 } /* Generic.Output */
body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
body .gs { font-weight: bold } /* Generic.Strong */
body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
body .gt { color: #0040D0 } /* Generic.Traceback */
body .kc { color: #954121 } /* Keyword.Constant */
body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
body .kp { color: #954121 } /* Keyword.Pseudo */
body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
body .kt { color: #B00040 } /* Keyword.Type */
body .m { color: #666666 } /* Literal.Number */
body .s { color: #219161 } /* Literal.String */
body .na { color: #7D9029 } /* Name.Attribute */
body .nb { color: #954121 } /* Name.Builtin */
body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
body .no { color: #880000 } /* Name.Constant */
body .nd { color: #AA22FF } /* Name.Decorator */
body .ni { color: #999999; font-weight: bold } /* Name.Entity */
body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
body .nf { color: #0000FF } /* Name.Function */
body .nl { color: #A0A000 } /* Name.Label */
body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
body .nt { color: #954121; font-weight: bold } /* Name.Tag */
body .nv { color: #19469D } /* Name.Variable */
body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
body .w { color: #bbbbbb } /* Text.Whitespace */
body .mf { color: #666666 } /* Literal.Number.Float */
body .mh { color: #666666 } /* Literal.Number.Hex */
body .mi { color: #666666 } /* Literal.Number.Integer */
body .mo { color: #666666 } /* Literal.Number.Oct */
body .sb { color: #219161 } /* Literal.String.Backtick */
body .sc { color: #219161 } /* Literal.String.Char */
body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
body .s2 { color: #219161 } /* Literal.String.Double */
body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
body .sh { color: #219161 } /* Literal.String.Heredoc */
body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
body .sx { color: #954121 } /* Literal.String.Other */
body .sr { color: #BB6688 } /* Literal.String.Regex */
body .s1 { color: #219161 } /* Literal.String.Single */
body .ss { color: #19469D } /* Literal.String.Symbol */
body .bp { color: #954121 } /* Name.Builtin.Pseudo */
body .vc { color: #19469D } /* Name.Variable.Class */
body .vg { color: #19469D } /* Name.Variable.Global */
body .vi { color: #19469D } /* Name.Variable.Instance */
body .il { color: #666666 } /* Literal.Number.Integer.Long */

Binary file not shown.

View File

@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory
*/
public class JLPMain {
public static final String VERSION = "1.2"
public static final String VERSION = "1.3"
private static Logger log = LoggerFactory.getLogger(JLPMain.class)
@ -79,7 +79,7 @@ public class JLPMain {
/// Get the CSS theme to use. We will start by assuming the default will
/// be used.
def css = JLPMain.class.getResourceAsStream("/jlp.css")
def css = JLPMain.class.getResourceAsStream("/css/jlp.css")
/// If the CSS file was specified on the command-line, let's look for it.
if (opts.'css-file') {
@ -102,10 +102,6 @@ public class JLPMain {
"${cssFile.canonicalPath}'."
println " Using the default CSS." }}
/// Extract the text from our css source (either an InputStream or a
/// File)
css = css.text
/// #### Create the input file list.
/// We will start with the filenames passed as arguments on the command

View File

@ -277,7 +277,8 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
swap(),
push(popAsString() + ((DocText) pop()).value))),
push(new Directive(popAsString(), popAsString(), popAsInt()))); }
push(new Directive(popAsString(), popAsString(), popAsInt(),
(DocBlock)peek()))); }
/**
* #### MLongDirective
@ -300,7 +301,8 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
swap(),
push(popAsString() + ((DocText) pop()).value))),
push(new Directive(popAsString(), popAsString(), popAsInt()))); }
push(new Directive(popAsString(), popAsString(), popAsInt(),
(DocBlock) peek()))); }
/**
* #### SShortDirective
@ -318,7 +320,8 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
FirstOf(AUTHOR_DIR, ORG_DIR, INCLUDE_DIR, COPYRIGHT_DIR), push(match()),
RemainingSDocLine(),
push(new Directive(match().trim(), popAsString(), popAsInt()))); }
push(new Directive(match().trim(), popAsString(), popAsInt(),
(DocBlock) peek()))); }
/**
* #### MShortDirective
@ -336,7 +339,8 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
FirstOf(AUTHOR_DIR, ORG_DIR, INCLUDE_DIR, COPYRIGHT_DIR), push(match()),
RemainingMDocLine(),
push(new Directive(match().trim(), popAsString(), popAsInt()))); }
push(new Directive(match().trim(), popAsString(), popAsInt(),
(DocBlock) peek()))); }
/**
* #### SDocText
@ -492,8 +496,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
boolean addToDocBlock(ASTNode an) {
DocBlock docBlock = (DocBlock) pop();
if (an instanceof Directive) {
docBlock.directives.add((Directive) an);
((Directive) an).parentBlock = docBlock; }
docBlock.directives.add((Directive) an); }
else if (an instanceof DocText) {
docBlock.docTexts.add((DocText) an); }
else { throw new IllegalStateException(); }

View File

@ -4,7 +4,7 @@
*/
package com.jdblabs.jlp
import com.jdblabs.jlp.ast.Directive
import com.jdblabs.jlp.ast.ASTNode
/**
* A *LinkAnchor* in the documentation is very similar to an HTML anchor. It
@ -20,10 +20,13 @@ import com.jdblabs.jlp.ast.Directive
*/
public class LinkAnchor {
public enum LinkType { OrgLink, FileLink }
/// The anchor id. This comes from the text after the directive.
public String id
public Directive directive
public LinkType type
public ASTNode source
public String sourceDocId
}

View File

@ -10,6 +10,8 @@ import com.jdblabs.jlp.ast.Directive.DirectiveType
import org.pegdown.Extensions
import org.pegdown.PegDownProcessor
import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4 as escape
import java.util.List
/**
@ -41,7 +43,7 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
case DirectiveType.Org:
LinkAnchor anchor = new LinkAnchor(
id: directive.value,
directive: directive,
source: directive,
sourceDocId: processor.currentDocId)
processor.linkAnchors[anchor.id] = anchor
@ -71,15 +73,41 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
"""<!DOCTYPE html>
<html>
<head>
<title>${processor.currentDocId}</title>
<title>${escape(processor.currentDocId)}</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="all" href="jlp.css"/>
<link type="text/css" rel="stylesheet" media="all"
href="${resolveLink('/css/jlp.css')}"></link>
<!-- syntax highlighting plugin -->
<link type="text/css" rel="stylesheet" media="all"
href="${resolveLink('/sh/styles/shCoreDefault.css')}"></link>
<script type="text/javascript"
src="${resolveLink('/sh/scripts/XRegExp.js')}"></script>
<script type="text/javascript"
src="${resolveLink('/sh/scripts/shCore.js')}"></script>""")
/// If there is a language-specific brush, include it
def shBrush = processor.shBrushForSourceType(
processor.currentDoc.sourceType)
if (shBrush) { sb.append("""
<script type="text/javascript"
src="${resolveLink('/sh/scripts/' + shBrush + '.js')}"></script>""") }
/// Finish our header and begin the body.
sb.append("""
<script type="text/javascript">
SyntaxHighlighter.defaults.light = true;
SyntaxHighlighter.defaults.unindent = false;
SyntaxHighlighter.all();
</script>
</head>
<body>
<div id="container">
<table cellpadding="0" cellspacing="0">
<thead><tr>
<th class="docs"><h1>${processor.currentDocId}</h1></th>
<th class="docs"><h1>${escape(processor.currentDocId)}</h1></th>
<th class="code"/>
</tr></thead>
<tbody>""")
@ -108,6 +136,7 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
/// Create the `tr` that will hold the `Block`. If we found an `@org`
/// directive we will add the id here.
// TODO: should this be escaped?
if (orgDir) { sb.append("\n<tr id='${orgDir.value}'>") }
else { sb.append("<tr>") }
@ -183,8 +212,9 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
codeLines = codeLines.collect { arr -> arr[1] }
/// Write out the lines in a <pre><code> block
return "<pre><code>${codeLines.join('')}</code></pre>" }
/// Write out the lines in a `<pre>` block
return "<pre class=\"brush: ${processor.currentDoc.sourceType};\">" +
"${escape(codeLines.join(''))}</pre>" }
/** @api Emit a *DocText*. */
protected String emit(DocText docText) { return docText.value }
@ -229,36 +259,13 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
/// Replace internal `jlp://` links with actual links based on`@org`
/// references.
html = html.replaceAll(/href=['"]jlp:\/\/([^\s"]+)['"]/) { wholeMatch, linkId ->
/// Get the org data we found in the parse phase for this org id.
def link = processor.linkAnchors[linkId]
String newLink
if (!link) {
// We do not have any reference to this id.
/* TODO: log error */
newLink = "href=\"broken_link(${linkId})\"" }
/// This link points to a location in this document.
else if (processor.currentDocId == link.sourceDocId) {
newLink = "href=\"#${linkId}\"" }
/// The link should point to a different document.
else {
TargetDoc thisDoc = processor.currentDoc
TargetDoc linkDoc = processor.docs[link.sourceDocId]
String pathToLinkedDoc = processor.getRelativeFilepath(
thisDoc.sourceFile.parentFile,
linkDoc.sourceFile)
/** The target document may not be in the same directory
* as us, backtrack to the (relative) top of our directory
* structure. */
newLink = 'href="' + pathToLinkedDoc + ".html#${linkId}\"" }
return newLink }
html = html.replaceAll(/href=['"](jlp:\/\/[^\s"']+)['"]/) { match, link->
return 'href="' + resolveLink(link) + '"' }
return html; }
/// Shortcut for `processor.resolveLink(url, processor.currentDoc)`.
protected String resolveLink(String url) {
processor.resolveLink(url, processor.currentDoc) }
}

View File

@ -4,6 +4,8 @@
*/
package com.jdblabs.jlp
import com.jdbernard.util.JarUtils
import java.util.jar.JarInputStream
import org.parboiled.BaseParser
import org.parboiled.Parboiled
import org.slf4j.Logger
@ -35,9 +37,9 @@ public class Processor {
/// The root of the output path.
public File outputRoot
/// The CSS that will be used for the resulting HTML documents. Note that
/// this is the CSS file contents, not the name of a CSS file.
public String css
/// The CSS that will be used for the resulting HTML documents. This object
/// can be any object that responds to the `text` property.
public def css
/// A shortcut for `docs[currentDocId]`
public TargetDoc currentDoc
@ -62,7 +64,7 @@ public class Processor {
* @api Process the input files given, writing the resulting documentation
* to the directory named in `outputDir`, using the CSS given in `css`
*/
public static void process(File outputDir, String css,
public static void process(File outputDir, def css,
List<File> inputFiles) {
/// Find the closest common parent folder to all of the files given.
@ -91,18 +93,34 @@ public class Processor {
/// Remember that the data for the processing run was initialized by the
/// constructor.
/// * Write the CSS file to our output directory.
File cssFile = new File(outputRoot, "css/jlp.css")
cssFile.parentFile.mkdirs()
cssFile.text = css.text
/// * Extract the syntax highlighter files to the output directory.
File shFile = new File(outputRoot, "temp.jar")
getClass().getResourceAsStream("/syntax-highlighter.jar").withStream { is ->
shFile.withOutputStream { os ->
while (is.available()) { os.write(is.read()) }}}
JarUtils.extract(shFile, outputRoot)
shFile.delete()
/// * Create the processing context for each input file. We are using
/// the relative path of the file as the document id.
inputFiles.each { file ->
def docId = getRelativeFilepath(inputRoot, file)
docs[docId] = new TargetDoc(sourceFile: file) }
docs[docId] = new TargetDoc(
sourceDocId: docId,
sourceFile: file,
sourceType: sourceTypeForFile(file)) }
/// * Run the parse phase on each of the files. For each file, we load
/// the parser for that file type and parse the file into an abstract
/// syntax tree (AST).
processDocs {
log.trace("Parsing '{}'.", currentDocId)
def parser = getParser(sourceTypeForFile(currentDoc.sourceFile))
def parser = getParser(currentDoc.sourceType)
// TODO: error detection
currentDoc.sourceAST = parser.parse(currentDoc.sourceFile.text) }
@ -111,7 +129,7 @@ public class Processor {
/// for an explanation of the generator phases).
processDocs {
log.trace("Second-pass parsing for '{}'.", currentDocId)
def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile))
def generator = getGenerator(currentDoc.sourceType)
// TODO: error detection
generator.parse(currentDoc.sourceAST) }
@ -119,7 +137,7 @@ public class Processor {
/// * Second pass by the generators, the emit phase.
processDocs {
log.trace("Emitting documentation for '{}'.", currentDocId)
def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile))
def generator = getGenerator(currentDoc.sourceType)
currentDoc.output = generator.emit(currentDoc.sourceAST) }
/// * Write the output to the output directory.
@ -138,10 +156,6 @@ public class Processor {
/// Create the directory for this file if it does not exist.
if (!outputDir.exists()) { outputDir.mkdirs() }
/// Write the CSS file if it does not exist.
File cssFile = new File(outputDir, "jlp.css")
if (!cssFile.exists()) { cssFile.withWriter{ it.println css } }
/// Copy the source file over.
// TODO: make this behavior customizable.
(new File(outputRoot, relativePath)).withWriter {
@ -164,6 +178,67 @@ public class Processor {
return c() } }
/***
* #### resolveLink
* Given a link, resolve it against the current output root.
*
* If this is a full URL, then we will attempt to resolve it based on the
* URL protocol. If this is not a full URL then it will resolve the link
* against the output root.
*
* `jlp://`
* : Resolve the link by looking for a matching link anchor defined
* in the documentation.
*
* *other protocol*
* : Return the link as-is.
*
* *absolute path (starts with `/`)*
* : Returns the link resolved directly against the output root.
*
* *relative path (no leading `/`)*
* : Returns the link resolved against the `TargetDoc` passed in.
*/
public String resolveLink(String link, TargetDoc targetDoc) {
switch (link) {
/// JLP link, let's resolve with a link anchor
case ~/^jlp:.*/:
/// Get the org data we found in the parse phase for this org id.
def m = (link =~ /jlp:\/\/(.+)/)
def linkId = m[0][1]
def linkAnchor = linkAnchors[m[0][1]]
if (!linkAnchor) {
// We do not have any reference to this id.
/* TODO: log error */
return "broken_link(${linkId})" }
/// This link points to a location in this document.
else if (targetDoc.sourceDocId == linkAnchor.sourceDocId) {
return "#${linkId}" }
/// The link should point to a different document.
else {
TargetDoc linkDoc = docs[linkAnchor.sourceDocId]
String pathToLinkedDoc = getRelativeFilepath(
targetDoc.sourceFile.parentFile, linkDoc.sourceFile)
return "${pathToLinkedDoc}.html#${linkId}" }
/// Other protocol: return as-is.
case ~/^\w+:.*/: return link
/// Absolute link, resolve relative to the output root.
case ~/^\/.*/: return outputRoot.canonicalPath + link
/// Relative link, resolve using the output root and the source
/// document relative to the input root.
default:
def relPath = getRelativePath(inputRoot, targetDoc.sourceFile)
return "${relPath}/${link}" }}
/**
* #### getRelativeFilepath
* Assuming our current directory is `root`, get the relative path to
@ -235,14 +310,31 @@ public class Processor {
/// Lookup the file type by extension
switch (extension) {
case 'c': case 'h': return 'c';
case 'c++': case 'h++': case 'cpp': case 'hpp': return 'c++';
case 'c++': case 'h++': case 'cpp': case 'hpp': return 'cpp';
case 'erl': case 'hrl': return 'erlang';
case 'groovy': return 'groovy';
case 'java': return 'java';
case 'js': return 'javascript';
case 'md': return 'markdown';
case 'html': return 'html';
case 'xml': case 'xhtml': return 'xml';
default: return 'unknown'; }}
/**
* #### shBrushForSourceType
* Lookup the syntax highlighter brush for the given source type.
* @org jlp.jdb-labs.com/Processor/shBrushForSourceType
*/
public static String shBrushForSourceType(String sourceType) {
switch (sourceType) {
case 'c': case 'cpp': return 'shBrushCpp'
case 'erlang': return 'shBrushErlang'
case 'groovy': return 'shBrushGroovy'
case 'java': return 'shBrushJava'
case 'javascript': return 'shBrushJScript'
case 'html': case 'xml': return 'shBrushXml'
default: return null }}
/**
* #### getGenerator
* Get a generator for the given source file type.
@ -278,8 +370,13 @@ public class Processor {
case 'markdown':
parsers[sourceType] = new MarkdownParser()
break
case 'html': case 'xml':
parsers[sourceType] = Parboiled.createParser(
JLPPegParser, '<!--', '-->',
'#$%^&*()_-+=|;:\'",<>?~`', '<<?')
break
case 'c':
case 'c++':
case 'cpp':
case 'groovy':
case 'java':
case 'javascript':

View File

@ -17,6 +17,11 @@ public class TargetDoc {
/// The original source file.
public File sourceFile
public String sourceDocId
/// The source code type (ie. `java`, `erlang`, etc.). See
/// [Processor.sourceTypeForFile](jlp.jdb-labs.com/Processor/sourceTypeForFile)
public String sourceType
public String output
}

View File

@ -5,7 +5,7 @@
package com.jdblabs.jlp.ast
/**
* ASTNode for *Block*s.
* ASTNode for *Blocks*.
* @org jlp.jdb-labs.com/ast/Block
*/
public class Block extends ASTNode {

View File

@ -59,10 +59,12 @@ public class Directive extends ASTNode {
public final DirectiveType type;
public final String value;
public Directive(String value, String typeString, int lineNumber) {
public Directive(String value, String typeString, int lineNumber,
DocBlock parentBlock) {
super(lineNumber)
this.value = value
this.type = DirectiveType.parse(typeString) }
this.type = DirectiveType.parse(typeString)
this.parentBlock = parentBlock }
public String toString() { "[${lineNumber}:Directive: ${type}, ${value}]" }
}

View File

@ -6,7 +6,8 @@ package com.jdblabs.jlp.ast
/**
* The top-level AST element. This represents a source file.
* @org jlp.jdb-labs.com/ast/SourceFile */
* @org jlp.jdb-labs.com/ast/SourceFile
*/
public class SourceFile {
/** The source file gets split into two parts during the initial parsing by