Skip to content
This repository has been archived by the owner on Oct 18, 2020. It is now read-only.

Commit

Permalink
make AST traverial more robust and easier to understand
Browse files Browse the repository at this point in the history
  • Loading branch information
dfrommi committed Sep 25, 2016
1 parent c7fb16d commit 6277d24
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 57 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apply plugin: 'groovy'
apply plugin: 'maven'

version = '0.6'
version = '0.7'

group = 'com.github.dfrommi'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.github.dfrommi.pandoc.types.HorizontalRule
import com.github.dfrommi.pandoc.types.Para
import com.github.dfrommi.pandoc.types.Space
import com.github.dfrommi.pandoc.types.Str
import spock.lang.IgnoreRest
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
Expand All @@ -29,7 +28,7 @@ class PandocFilterSpec extends Specification {
System.out = oldSystemOut
}

@Unroll @IgnoreRest
@Unroll //@IgnoreRest
def "toJSONFilter transformation #action elements"() {
given:
String input = converter.mdToJsonText(mdInput)
Expand All @@ -45,19 +44,19 @@ class PandocFilterSpec extends Specification {
String result = outContent.toString()
def actualJson = converter.jsonTextToJson(result)

expectedJson == actualJson
actualJson == expectedJson

where:
[action, mdInput, mdExpectedResult, transformation] << [
// ["replaces", "Hello world", "HELLO WORLD", { if(it in Str) new Str(it.text.toUpperCase()) }],
// ["removes text", "Hello world", "Hello", { if((it in Str && it.text == "world") || it in Space) []} ],
// ["adds", "Hello world", "Good morning world", { if (it in Str && it.text == "Hello") [new Str("Good"), new Space(), new Str("morning")] }],
["replaces multiple", "# Some header\n\nSome text,", "Header line 1\nHeader line 2", { if(it in Header) [new Para(new Str("Header line 1")), new Para(new Str("Header line 2"))] }],
// ["removes","# Header\n\nSome text.\n\n-----\n\nSome more text", "# Header\n\nSome text.\n\nSome more text", {if(it in HorizontalRule) [] } ]
["replaces", "Hello world", "HELLO WORLD", { if(it in Str) new Str(it.text.toUpperCase()) }],
["removes text", "Hello world", "Hello", { if((it in Str && it.text == "world") || it in Space) []} ],
["adds", "Hello world", "Good morning world", { if (it in Str && it.text == "Hello") [new Str("Good"), new Space(), new Str("morning")] }],
["replaces multiple", "# Some header\n\nSome text", "Header1\n\nHeader2\n\nSome text", { if(it in Header) [new Para(new Str("Header1")), new Para(new Str("Header2"))] }],
["removes","# Header\n\nSome text.\n\n-----\n\nSome more text", "# Header\n\nSome text.\n\nSome more text", {if(it in HorizontalRule) [] } ]
]
}

def "toJSONFilter processes elements in correct sequence"() {
def "toJSONFilter processes elements in DFS sequence"() {
given:
String input = converter.mdToJsonText(mdInput)
def classSequence = []
Expand All @@ -75,30 +74,30 @@ class PandocFilterSpec extends Specification {
where:
mdInput | expectedSequence
"Hello world" | [Para, Str, Space, Str]
"# Header\n\nSome paragraph\n\n```\nSome code\n```\n" | [Header, Para, CodeBlock, Str /*from header*/, Str, Space, Str]
"# Header\n\nSome paragraph\n\n```\nSome code\n```\n" | [Header, Str, Para, Str, Space, Str, CodeBlock]
}

def "toJSONFilter transformation copies elements"() {
@Unroll
def "toJSONFilter transformation of test document #i transforms successfully"() {
given:
String mdInput = this.getClass().getResourceAsStream("/test-${i}.md").text
String input = converter.mdToJsonText(mdInput)
def expectedJson = converter.jsonTextToJson(input)

def outContent = hijackStreams(input)
String mdInput = this.getClass().getResourceAsStream("/test-${i}.md").text
String input = converter.mdToJsonText(mdInput)
def expectedJson = converter.jsonTextToJson(input)
def outContent = hijackStreams(input)

when:
filter.toJSONFilter { elem ->
elem
}
filter.toJSONFilter { elem ->
elem
}

then:
String result = outContent.toString()
def actualJson = converter.jsonTextToJson(result)
String result = outContent.toString()
def actualJson = converter.jsonTextToJson(result)

expectedJson == actualJson
actualJson == expectedJson

where:
i << (1..13)
i << (1..13)
}

static private OutputStream hijackStreams(String inputText) {
Expand Down
64 changes: 32 additions & 32 deletions src/main/groovy/com/github/dfrommi/pandoc/PandocFilter.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.dfrommi.pandoc

import com.github.dfrommi.pandoc.convert.PandocConverter
import com.github.dfrommi.pandoc.types.PandocType
import com.github.dfrommi.pandoc.util.Walkable

class PandocFilter {
/**
Expand Down Expand Up @@ -90,45 +91,44 @@ class PandocFilter {
* @param node current mode
* @param meta Metadata object
* @param action The action closure to perform on node objects
* @return The modified node
* @return The modified node, never `null`
*/
def walk(node, meta, action) {
def res
if(node in List) {
res = node.collect {
def transformedResult = callActionWithOptionalMeta(it, meta, action)
transformedResult
}.findAll { it != [] }
} else {
res = callActionWithOptionalMeta(node, meta, action)
}
// If list came in, list is also returned
if(isCollectionOrArray(node)) {
def nodeList = node as List
def transformedNodeList = nodeList.collectMany { // a single node can be replaced by multiple, therefore collectMany
def currentResult = walk(it, meta, action)
// Automatically flatten if one node has been replaced by multiple nodes
(!isCollectionOrArray(it) && isCollectionOrArray(currentResult)) ? currentResult as List : [currentResult]
}
return transformedNodeList
}

// Transformation of a single node

def transformedResult = callActionWithOptionalMeta(node, meta, action)

(res as List).each { currentRes ->
currentRes.children.each{ key, childValues ->
// key is property name
def replacedSingleByList = false
// `null` means no modification.
// Result [] (=removing element) is handled in the caller (see collectMany)
if (transformedResult == null) {
transformedResult = node
}

def walkResult
def childResult = childValues.collect { child ->
if (isCollectionOrArray(child)) {
walkResult = child.collect { walk(it, meta, action) }
} else {
walkResult = walk(child, meta, action)
// ?: not possible, because false has to return walkResult
if(walkResult in List && !(child in List)) {
replacedSingleByList = true
}
}
// Transform all children

walkResult != null ? walkResult : child
}
currentRes[key] = replacedSingleByList ? childResult.flatten() : childResult
// transformation can be one node or a node list, but never a deeply nested list
(transformedResult as List).each { Walkable transformedNode ->
transformedNode.children.each { propertyName, childValues ->
// key is the property name, value is the child or list of children
def childResult = walk(childValues, meta, action)
transformedNode."$propertyName" = childResult
}
}
res
}

transformedResult
}

private callActionWithOptionalMeta(node, meta, action) {
def actionResult = hasMetaParam(action) ? action(node, meta) : action(node)
actionResult != null ? actionResult : node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ trait Walkable {
* Map of child objects
* @return Map with property name as key and child value list as value
*/
def getChildren() {
Map<String, ?> getChildren() {
def children = [:]
AnnotationHelper.findAllFieldsWithAnnotation(this.class, Child).each { AnnotatedFieldInfo<Child> afi ->
children << [(afi.name): afi.getFieldValue(this)]
Expand Down

0 comments on commit 6277d24

Please sign in to comment.