finally got preprocessor fixed

This commit is contained in:
Tobe O 2018-11-10 16:37:00 -05:00
parent f1f4053dde
commit 30fc96851b
2 changed files with 229 additions and 193 deletions

View file

@ -36,7 +36,12 @@ Future<Document> resolve(Document document, Directory currentDirectory,
/// Folds any `extend` declarations. /// Folds any `extend` declarations.
Future<Document> applyInheritance(Document document, Directory currentDirectory, Future<Document> applyInheritance(Document document, Directory currentDirectory,
void onError(JaelError error), Iterable<Patcher> patch) async { void onError(JaelError error), Iterable<Patcher> patch) async {
if (document.root.tagName.name != 'extend') return document; if (document.root.tagName.name != 'extend') {
// This is not an inherited template, so just fill in the existing blocks.
var root =
replaceChildrenOfElement(document.root, {}, onError, true, false);
return new Document(document.doctype, root);
}
var element = document.root; var element = document.root;
var attr = var attr =
@ -52,95 +57,210 @@ Future<Document> applyInheritance(Document document, Directory currentDirectory,
element.tagName.span)); element.tagName.span));
return null; return null;
} else { } else {
// First, we need to identify the root template. // In theory, there exists:
var chain = new Queue<Document>(); // * A single root template with a number of blocks
// * Some amount of <extend src="..."> templates.
while (document != null) { // To produce an accurate representation, we need to:
chain.addFirst(document); // 1. Find the root template, and store a copy in a variable.
var parent = getParent(document, onError); // 2: For each <extend> template:
if (parent == null) break; // a. Enumerate the block overrides it defines
var file = currentDirectory.fileSystem // b. Replace matching blocks in the current document
.file(currentDirectory.uri.resolve(parent)); // c. If there is no block, and this is the LAST <extend>, fill in the default block content.
var contents = await file.readAsString(); var hierarchy = await resolveHierarchy(document, currentDirectory, onError);
document = parseDocument(contents, sourceUrl: file.uri, onError: onError); var out = hierarchy.root;
//document = await resolveIncludes(document, file.parent, onError);
document = await resolve(document, currentDirectory, if (out is! RegularElement) {
onError: onError, patch: patch); return hierarchy.rootDocument;
} }
// Then, for each referenced template, in order, transform the last template Element setOut(Element out, Map<String, RegularElement> definedOverrides,
// by filling in blocks. bool anyTemplatesRemain) {
document = chain.removeFirst(); var children = <ElementChild>[];
var blocks = new SymbolTable<Element>(); // Replace matching blocks, etc.
for (var c in out.children) {
while (chain.isNotEmpty) { if (c is Element) {
var child = chain.removeFirst(); children.addAll(replaceBlocks(
var scope = blocks; c, definedOverrides, onError, false, anyTemplatesRemain));
extractBlockDeclarations(scope, child.root, onError); } else {
children.add(c);
var blocksExpanded = }
await expandBlocks(document.root, blocks, currentDirectory, onError);
if (blocksExpanded == null) {
// When this returns null, we've reached a block declaration.
// Just continue.
continue;
} }
document = var root = hierarchy.root as RegularElement;
new Document(child.doctype ?? document.doctype, blocksExpanded); return new RegularElement(root.lt, root.tagName, root.attributes, root.gt,
children, root.lt2, root.slash, root.tagName2, root.gt2);
} }
// Fill in any remaining blocks // Loop through all extends, filling in blocks.
var blocksExpanded = while (hierarchy.extendsTemplates.isNotEmpty) {
await expandBlocks(document.root, blocks, currentDirectory, onError); var tmpl = hierarchy.extendsTemplates.removeFirst();
return new Document(document.doctype, blocksExpanded); var definedOverrides = findBlockOverrides(tmpl, onError);
if (definedOverrides == null) break;
out =
setOut(out, definedOverrides, hierarchy.extendsTemplates.isNotEmpty);
}
// Lastly, just default-fill any remaining blocks.
var definedOverrides = findBlockOverrides(out, onError);
if (definedOverrides != null) out = setOut(out, definedOverrides, false);
// Return our processed document.
return new Document(document.doctype, out);
} }
} }
List<Element> allBlocksRecursive(Element element) { Map<String, RegularElement> findBlockOverrides(
var out = <Element>[]; Element tmpl, void onError(JaelError e)) {
var out = <String, RegularElement>{};
for (var e in element.children) { for (var child in tmpl.children) {
if (e is Element && e.tagName.name == 'block') { if (child is RegularElement && child.tagName?.name == 'block') {
out.add(e); var name = child.attributes
.firstWhere((a) => a.name == 'name', orElse: () => null)
?.value
?.compute(new SymbolTable()) as String;
if (name?.trim()?.isNotEmpty == true) {
out[name] = child;
}
} }
} }
for (var child in element.children) { return out;
if (child is Element) {
var childBlocks = allBlocksRecursive(child);
out.addAll(childBlocks);
}
}
return out; //out.reversed.toList();
} }
/// Extracts any `block` declarations. /// Resolves the document hierarchy at a given node in the tree.
void extractBlockDeclarations(SymbolTable<Element> blocks, Element element, Future<DocumentHierarchy> resolveHierarchy(Document document,
void onError(JaelError error)) { Directory currentDirectory, void onError(JaelError e)) async {
//var blockElements = allBlocksRecursive(element); var extendsTemplates = new Queue<Element>();
var blockElements = String parent;
element.children.where((e) => e is Element && e.tagName.name == 'block');
for (Element blockElement in blockElements) { while (document != null && (parent = getParent(document, onError)) != null) {
var nameAttr = blockElement.attributes extendsTemplates.addFirst(document.root);
var file = currentDirectory.childFile(parent);
var parsed = parseDocument(await file.readAsString(),
sourceUrl: file.uri, onError: onError);
document = await resolveIncludes(parsed, currentDirectory, onError);
}
if (document == null) return null;
return new DocumentHierarchy(document, extendsTemplates);
}
class DocumentHierarchy {
final Document rootDocument;
final Queue<Element> extendsTemplates; // FIFO
DocumentHierarchy(this.rootDocument, this.extendsTemplates);
Element get root => rootDocument.root;
}
Iterable<ElementChild> replaceBlocks(
Element element,
Map<String, RegularElement> definedOverrides,
void onError(JaelError e),
bool replaceWithDefault,
bool anyTemplatesRemain) {
if (element.tagName.name == 'block') {
var nameAttr = element.attributes
.firstWhere((a) => a.name == 'name', orElse: () => null); .firstWhere((a) => a.name == 'name', orElse: () => null);
if (nameAttr == null) { var name = nameAttr?.value?.compute(new SymbolTable());
onError(new JaelError(JaelErrorSeverity.warning,
'Missing "name" attribute in "block" tag.', blockElement.span)); if (name?.trim()?.isNotEmpty != true) {
} else if (nameAttr.value is! StringLiteral) {
onError(new JaelError( onError(new JaelError(
JaelErrorSeverity.warning, JaelErrorSeverity.warning,
'The "name" attribute in an "block" tag must be a string literal.', 'This <block> has no `name` attribute, and will be outputted as-is.',
nameAttr.span)); element.span));
return [element];
} else if (!definedOverrides.containsKey(name)) {
if (element is RegularElement) {
if (anyTemplatesRemain || !replaceWithDefault) {
// If there are still templates remaining, this current block may eventually
// be resolved. Keep it alive.
// We can't get rid of the block itself, but it may have blocks as children...
var inner = allChildrenOfRegularElement(element, definedOverrides,
onError, replaceWithDefault, anyTemplatesRemain);
return [
new RegularElement(
element.lt,
element.tagName,
element.attributes,
element.gt,
inner,
element.lt2,
element.slash,
element.tagName2,
element.gt2)
];
} else {
// Otherwise, just return the default contents.
return element.children;
}
} else {
return [element];
}
} else { } else {
var name = (nameAttr.value as StringLiteral).value; return allChildrenOfRegularElement(definedOverrides[name],
blocks.assign(name, blockElement); definedOverrides, onError, replaceWithDefault, anyTemplatesRemain);
}
} else if (element is SelfClosingElement) {
return [element];
} else {
return [
replaceChildrenOfRegularElement(element as RegularElement,
definedOverrides, onError, replaceWithDefault, anyTemplatesRemain)
];
}
}
Element replaceChildrenOfElement(
Element el,
Map<String, RegularElement> definedOverrides,
void onError(JaelError e),
bool replaceWithDefault,
bool anyTemplatesRemain) {
if (el is RegularElement) {
return replaceChildrenOfRegularElement(
el, definedOverrides, onError, replaceWithDefault, anyTemplatesRemain);
} else {
return el;
}
}
RegularElement replaceChildrenOfRegularElement(
RegularElement el,
Map<String, RegularElement> definedOverrides,
void onError(JaelError e),
bool replaceWithDefault,
bool anyTemplatesRemain) {
var children = allChildrenOfRegularElement(
el, definedOverrides, onError, replaceWithDefault, anyTemplatesRemain);
return new RegularElement(el.lt, el.tagName, el.attributes, el.gt, children,
el.lt2, el.slash, el.tagName2, el.gt2);
}
List<ElementChild> allChildrenOfRegularElement(
RegularElement el,
Map<String, RegularElement> definedOverrides,
void onError(JaelError e),
bool replaceWithDefault,
bool anyTemplatesRemain) {
var children = <ElementChild>[];
for (var c in el.children) {
if (c is Element) {
children.addAll(replaceBlocks(c, definedOverrides, onError,
replaceWithDefault, anyTemplatesRemain));
} else {
children.add(c);
} }
} }
return children;
} }
/// Finds the name of the parent template. /// Finds the name of the parent template.
@ -165,122 +285,6 @@ String getParent(Document document, void onError(JaelError error)) {
} }
} }
/// Replaces any `block` tags within the element.
Future<Element> expandBlocks(Element element, SymbolTable<Element> outerScope,
Directory currentDirectory, void onError(JaelError error)) async {
var innerScope = outerScope.createChild();
if (element is SelfClosingElement)
return element;
else if (element is RegularElement) {
if (element.children.isEmpty) return element;
var expanded = new Set<ElementChild>();
element.children.forEach((e) => print(e.span.highlight()));
for (var child in element.children) {
if (child is Element) {
if (child is SelfClosingElement)
expanded.add(child);
else if (child is RegularElement) {
if (child.tagName.name != 'block') {
expanded.add(child);
} else {
var nameAttr = child.attributes
.firstWhere((a) => a.name == 'name', orElse: () => null);
if (nameAttr == null) {
onError(new JaelError(JaelErrorSeverity.warning,
'Missing "name" attribute in "block" tag.', child.span));
} else if (nameAttr.value is! StringLiteral) {
onError(new JaelError(
JaelErrorSeverity.warning,
'The "name" attribute in an "block" tag must be a string literal.',
nameAttr.span));
}
var name = (nameAttr.value as StringLiteral).value;
Iterable<ElementChild> children;
if (innerScope.resolve(name) == null) {
print('Why is $name not defined?');
children = []; //child.children;
} else {
children = innerScope.resolve(name).value.children;
}
expanded.addAll(children);
}
} else {
throw new UnsupportedError(
'Unsupported element type: ${element.runtimeType}');
}
} else {
expanded.add(child);
}
}
// Resolve all includes...
var out = <ElementChild>[];
for (var c in expanded) {
if (c is Element) {
var blocksExpanded =
await expandBlocks(c, innerScope, currentDirectory, onError);
var nameAttr = c.attributes
.firstWhere((a) => a.name == 'name', orElse: () => null);
var name = (nameAttr?.value is StringLiteral)
? ((nameAttr.value as StringLiteral).value)
: null;
if (name == null) {
out.add(blocksExpanded);
} else {
// This element itself resolved to a block; expand it.
out.addAll(innerScope.resolve(name)?.value?.children ?? <ElementChild>[]);
}
} else {
out.add(c);
}
}
var finalElement = new RegularElement(
element.lt,
element.tagName,
element.attributes,
element.gt,
out,
element.lt2,
element.slash,
element.tagName2,
element.gt2);
// If THIS element is a block, it needs to be expanded as well.
if (element.tagName.name == 'block') {
var nameAttr = element.attributes
.firstWhere((a) => a.name == 'name', orElse: () => null);
if (nameAttr == null) {
onError(new JaelError(JaelErrorSeverity.warning,
'Missing "name" attribute in "block" tag.', element.span));
} else if (nameAttr.value is! StringLiteral) {
onError(new JaelError(
JaelErrorSeverity.warning,
'The "name" attribute in an "block" tag must be a string literal.',
nameAttr.span));
}
var name = (nameAttr.value as StringLiteral).value;
outerScope.assign(name, finalElement);
throw outerScope.allVariables.map((v) => v.name);
} else {
return finalElement;
}
} else {
throw new UnsupportedError(
'Unsupported element type: ${element.runtimeType}');
}
}
/// Expands all `include[src]` tags within the template, and fills in the content of referenced files. /// Expands all `include[src]` tags within the template, and fills in the content of referenced files.
Future<Document> resolveIncludes(Document document, Directory currentDirectory, Future<Document> resolveIncludes(Document document, Directory currentDirectory,
void onError(JaelError error)) async { void onError(JaelError error)) async {

View file

@ -17,13 +17,17 @@ main() {
// b.jl // b.jl
fileSystem.file('b.jl').writeAsStringSync( fileSystem.file('b.jl').writeAsStringSync(
'<i><include src="a.jl"><block name="greeting"><p>Hello</p></block></i>'); '<i><include src="a.jl" /><block name="greeting"><p>Hello</p></block></i>');
// c.jl // c.jl
// NOTE: This SHOULD NOT produce "yes", because the only children expanded within an <extend>
// are <block>s.
fileSystem.file('c.jl').writeAsStringSync( fileSystem.file('c.jl').writeAsStringSync(
'<extend src="b.jl"><block name="greeting">Goodbye</block>Yes</extend>'); '<extend src="b.jl"><block name="greeting">Goodbye</block>Yes</extend>');
// d.jl // d.jl
// This should not output "Yes", either.
// It should actually produce the same as c.jl, since it doesn't define any unique blocks.
fileSystem.file('d.jl').writeAsStringSync( fileSystem.file('d.jl').writeAsStringSync(
'<extend src="c.jl"><block name="greeting">Saluton!</block>Yes</extend>'); '<extend src="c.jl"><block name="greeting">Saluton!</block>Yes</extend>');
@ -33,7 +37,7 @@ main() {
// fox.jl // fox.jl
fileSystem.file('fox.jl').writeAsStringSync( fileSystem.file('fox.jl').writeAsStringSync(
'<block name="dance">The name is <block name="name"></block></block>'); '<div><block name="dance">The name is <block name="name">default-name</block></block></div>');
// trot.jl // trot.jl
fileSystem.file('trot.jl').writeAsStringSync( fileSystem.file('trot.jl').writeAsStringSync(
@ -63,13 +67,41 @@ main() {
<b> <b>
a.jl a.jl
</b> </b>
GoodbyeYes Goodbye
</i> </i>
''' '''
.trim()); .trim());
}); });
test('block resolution is recursive', () async { test('block defaults are emitted', () async {
var file = fileSystem.file('b.jl');
var original = jael.parseDocument(await file.readAsString(),
sourceUrl: file.uri, onError: (e) => throw e);
var processed = await jael.resolve(
original, fileSystem.directory(fileSystem.currentDirectory),
onError: (e) => throw e);
var buf = new CodeBuffer();
var scope = new SymbolTable();
const jael.Renderer().render(processed, buf, scope);
print(buf);
expect(
buf.toString(),
'''
<i>
<b>
a.jl
</b>
<p>
Hello
</p>
</i>
'''
.trim());
});
test('block resolution only redefines blocks at one level at a time',
() async {
var file = fileSystem.file('d.jl'); var file = fileSystem.file('d.jl');
var original = jael.parseDocument(await file.readAsString(), var original = jael.parseDocument(await file.readAsString(),
sourceUrl: file.uri, onError: (e) => throw e); sourceUrl: file.uri, onError: (e) => throw e);
@ -88,7 +120,7 @@ main() {
<b> <b>
a.jl a.jl
</b> </b>
Saluton!Yes Goodbye
</i> </i>
''' '''
.trim()); .trim());
@ -109,12 +141,12 @@ main() {
expect( expect(
buf.toString(), buf.toString(),
''' '''
<i> <div>
<b> The name is CONGA
a.jl <i>
</b> framework
Angel frameworkGoodbye </i>
</i> </div>
''' '''
.trim()); .trim());
}); });