Skip to content

Commit

Permalink
Merge pull request #3 from uaraven/graph-diagram
Browse files Browse the repository at this point in the history
Add Graphiz dot graph generator
  • Loading branch information
uaraven committed Oct 25, 2020
2 parents b83a638 + 5b66543 commit cbbae92
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 60 deletions.
18 changes: 17 additions & 1 deletion NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,20 @@ Redistribution and use in source and binary forms, with or without modification,
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
```

# Graphviz-java

[GitHub](https://github.com/nidi3/graphviz-java)

[Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt)

## GraalVM JS

Copyright (c) 2019, Oracle and/or its affiliates.

[GitHub](https://github.com/graalvm/graaljs)

Licenses:
- [Universal Permissive License, Version 1.0](http://opensource.org/licenses/UPL)
- [MIT License](http://opensource.org/licenses/MIT)
14 changes: 12 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,31 @@ plugins {
mainClassName = 'net.ninjacat.headlights.AppKt'

group 'net.ninjacat'
version '0.3.0'
version '0.4.0'

repositories {
mavenCentral()
}

javafx {
version = "14"
modules = [ 'javafx.controls']
modules = ['javafx.controls']
}

ext {
j2v8Version = '4.6.0'
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation 'org.antlr:antlr4:4.8-1'
implementation 'org.antlr:antlr4-runtime:4.8-1'
implementation 'org.fxmisc.richtext:richtextfx:0.10.5'
implementation 'guru.nidi:graphviz-java:0.17.0'
compile 'org.graalvm.js:js:20.2.0'

implementation 'ch.qos.logback:logback-classic:1.2.3'

testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
testImplementation 'org.assertj:assertj-core:3.17.2'
}
Expand Down
Binary file modified headlights.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 40 additions & 9 deletions src/main/kotlin/net/ninjacat/headlights/App.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.ninjacat.headlights

import ch.qos.logback.classic.Level
import javafx.application.Application
import javafx.collections.FXCollections
import javafx.event.EventHandler
Expand All @@ -20,16 +21,18 @@ import javafx.stage.Stage
import net.ninjacat.headlights.antlr.*
import net.ninjacat.headlights.ui.GrammarTextEditorPane
import net.ninjacat.headlights.ui.OutputPane
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.function.Consumer
import kotlin.system.exitProcess


class AntlrViewApp : Application() {
private val editors = GrammarTextEditorPane()
private val outputPane: OutputPane = OutputPane()
private val resultPane: VBox = VBox()
private val mainMenu = MenuBar()
private val antlrMode = ComboBox<String>(FXCollections.observableArrayList("Interpreted", "Compiled"))
private val exportDotMenu = MenuItem("Export dot file")
private val antlrMode = ComboBox(FXCollections.observableArrayList("Interpreted", "Compiled"))

override fun start(primaryStage: Stage) {
primaryStage.title = "ANTLR in the Headlights"
Expand All @@ -38,9 +41,6 @@ class AntlrViewApp : Application() {

outputPane.onErrorClick = Consumer { error -> showError(error) }

resultPane.children?.add(outputPane)
VBox.setVgrow(outputPane, Priority.ALWAYS)

if (parameters.named.containsKey("grammar")) {
editors.loadGrammar(parameters.named["grammar"])
}
Expand All @@ -52,7 +52,7 @@ class AntlrViewApp : Application() {
content.orientation = Orientation.VERTICAL
content.items.addAll(
vboxOf(growing = editors, editors, createBottomBar()),
resultPane
outputPane
)

createMainMenu(mainMenu, primaryStage)
Expand Down Expand Up @@ -89,10 +89,16 @@ class AntlrViewApp : Application() {

val exitMenuItem = MenuItem("E_xit")

val graphMenu = Menu("_Graph")

val grammarExtensions = listOf(
FileChooser.ExtensionFilter("Antlr4 Grammar files", "*.g4"),
FileChooser.ExtensionFilter("All files", "*.*")
)
val dotExtensions = listOf(
FileChooser.ExtensionFilter("Dot files", "*.dot"),
FileChooser.ExtensionFilter("All files", "*.*")
)

loadGrammarMenuItem.onAction = EventHandler {
val fileChooser = FileChooser()
Expand Down Expand Up @@ -138,6 +144,20 @@ class AntlrViewApp : Application() {

exitMenuItem.onAction = EventHandler { exitProcess(0); }

exportDotMenu.isDisable = true
exportDotMenu.onAction = EventHandler {
if (outputPane.getParseTree() != null) {
val fileChooser = FileChooser()
fileChooser.title = "Select dot file"
fileChooser.extensionFilters.addAll(dotExtensions)
val file = fileChooser.showSaveDialog(stage)
if (file != null) {
val dotFile = GraphvizGen.generateDotFile(outputPane.getParseTree()!!, export = true)
file.writeText(dotFile)
}
}
}

fileMenu.items.addAll(
loadGrammarMenuItem,
loadTextMenuItem,
Expand All @@ -149,8 +169,9 @@ class AntlrViewApp : Application() {
SeparatorMenuItem(),
exitMenuItem
)
graphMenu.items.addAll(exportDotMenu)

menu.menus.addAll(fileMenu)
menu.menus.addAll(fileMenu, graphMenu)
}

private fun vboxOf(growing: Node?, vararg children: Node): Node {
Expand Down Expand Up @@ -196,9 +217,11 @@ class AntlrViewApp : Application() {
outputPane.clearTokens()
}
if (parser.hasTree()) {
outputPane.showTree(parser.parseTree()!!, parser.ruleNames().asList())
outputPane.showTree(parser.parseTree()!!)
exportDotMenu.isDisable = false
} else {
outputPane.clearTree()
exportDotMenu.isDisable = true
}
if (parser.errors().isNotEmpty()) {
outputPane.showErrors(parser.errors())
Expand All @@ -216,7 +239,7 @@ class AntlrViewApp : Application() {
ErrorMessage(-1, -1, ex.message, ErrorSource.UNKNOWN)
)
)
ex.printStackTrace()
LOGGER.error("Failed to process grammar", ex)
}
}

Expand All @@ -230,9 +253,17 @@ class AntlrViewApp : Application() {
}
}

companion object {
private val LOGGER: Logger = LoggerFactory.getLogger("headlights")
}


}

fun main(vararg args: String) {

val root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as ch.qos.logback.classic.Logger
root.level = Level.ERROR

Application.launch(AntlrViewApp::class.java, *args)
}
74 changes: 74 additions & 0 deletions src/main/kotlin/net/ninjacat/headlights/GraphvizGen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package net.ninjacat.headlights

import net.ninjacat.headlights.antlr.ParseTreeError
import net.ninjacat.headlights.antlr.ParseTreeNode
import net.ninjacat.headlights.antlr.ParseTreeTerminal

object GraphvizGen {

private val displayStyles = mapOf(
"bg" to "dimgray",
"bg_node" to "azure",
"edge_color" to "lightsteelblue",
"error_text" to "red",
"error_stroke" to "orangered",
"error_shape" to "note",
"error_style" to "filled",
"terminal_text" to "navyblue",
"terminal_stroke" to "blue",
"terminal_shape" to "box",
"terminal_style" to "filled",
"rule_text" to "black",
"rule_stroke" to "white",
"rule_shape" to "box",
"rule_style" to "\"filled,rounded\"",
)

private val exportStyles = mapOf(
"bg" to "white",
"bg_node" to "white",
"edge_color" to "black",
"error_text" to "red",
"error_stroke" to "orangered",
"error_shape" to "note",
"error_style" to "\"\"",
"terminal_text" to "navyblue",
"terminal_stroke" to "blue",
"terminal_shape" to "box",
"terminal_style" to "\"\"",
"rule_text" to "black",
"rule_stroke" to "black",
"rule_shape" to "box",
"rule_style" to "rounded",
)

private var activeStyle = exportStyles

fun generateDotFile(root: ParseTreeNode, export: Boolean = false): String {
val nodes = mutableListOf<String>()
val edges = mutableListOf<String>()
activeStyle = if (export) exportStyles else displayStyles

processNodes(root, nodes, edges)
return "graph ParseTree {\nbgcolor=${activeStyle["bg"]}\n" + nodes.joinToString("\n") + "\n" + edges.joinToString("\n") + "\n}\n"
}

private fun nodeStyle(nodeType: String): String {
return "color=${activeStyle[nodeType + "_stroke"]} style=${activeStyle[nodeType+"_style"]} " +
"fillcolor=${activeStyle["bg_node"]} fontcolor=${activeStyle[nodeType + "_text"]} " +
"shape=${activeStyle[nodeType + "_shape"]}"
}

private fun processNodes(node: ParseTreeNode, nodes: MutableList<String>, edges: MutableList<String>) {
when (node) {
is ParseTreeError -> nodes.add("\"${node.text}_${node.id}\" [${nodeStyle("error")} label=\"${node.text}\"]")
is ParseTreeTerminal -> nodes.add("\"${node.text}_${node.id}\" [${nodeStyle("terminal")} label=\"${node.text}\"]")
else -> nodes.add("\"${node.text}_${node.id}\" [${nodeStyle("rule")} label=\"${node.text}\"]")
}
val edgeIds = node.children.map { "\"${it.text}_${it.id}\"" }.joinToString(" ")
if (node.children.isNotEmpty()) {
edges.add("\"${node.text}_${node.id}\" -- {$edgeIds} [color=${activeStyle["edge_color"]}]")
}
node.children.forEach { processNodes(it, nodes, edges) }
}
}
14 changes: 11 additions & 3 deletions src/main/kotlin/net/ninjacat/headlights/antlr/AntlrCompiler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package net.ninjacat.headlights.antlr
import org.antlr.v4.Tool
import org.antlr.v4.tool.Grammar
import org.antlr.v4.tool.ast.GrammarRootAST
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
Expand All @@ -24,11 +26,14 @@ class AntlrCompiler(grammar: String, text: String, private val javaCompiler: Jav
val packageName = extractPackageName()

val path = saveGrammar(grammarName)
val antlrTool = Tool()

val toolListener = ToolListener(errors)

if (Thread.currentThread().contextClassLoader == null) {
Thread.currentThread().contextClassLoader = ClassLoader.getSystemClassLoader()
}
val antlrTool = Tool()
antlrTool.addListener(toolListener)

runTool(antlrTool, path)

if (errors.isEmpty()) {
Expand All @@ -39,14 +44,15 @@ class AntlrCompiler(grammar: String, text: String, private val javaCompiler: Jav
val runner = AntlrExecutor(text, packageName, grammarName, path.parent.resolve("classes"))
val results = runner.parse(errorListener)
tokens = results.tokens!!
tree = results.tree
tree = convertParseTree(results.tree!!, results.ruleNames)
ruleNames = results.ruleNames

}
}
return errors.isEmpty()
} catch (ex: Exception) {
errors.add(ErrorMessage(-1, -1, ex.message, ErrorSource.GENERATED_PARSER))
LOGGER.error("Error while compiling ANTLR grammar", ex)
}
return false
}
Expand Down Expand Up @@ -90,6 +96,8 @@ class AntlrCompiler(grammar: String, text: String, private val javaCompiler: Jav
}

companion object {
val LOGGER: Logger = LoggerFactory.getLogger("headlights.antlr.compiler")

val grammarNameExtractor: Pattern =
Pattern.compile("(?:(?:lexer|parser)\\s+)?grammar\\s+([a-zA-Z_]\\w*);", Pattern.DOTALL)

Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/net/ninjacat/headlights/antlr/AntlrGen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class ToolListener(private val errors: MutableList<ErrorMessage>) : ANTLRToolLis

abstract class AntlrGrammarParser(val grammar: String, val text: String): Closeable {
protected val errors = mutableListOf<ErrorMessage>()
protected var tree: ParseTree? = null
protected var tree: ParseTreeNode? = null
protected var tokens: List<LexerToken> = listOf()
protected var antlrGrammar: Grammar? = null
protected var ruleNames: Array<String> = arrayOf()
Expand All @@ -103,7 +103,7 @@ abstract class AntlrGrammarParser(val grammar: String, val text: String): Closea

fun tokens() = tokens

fun parseTree(): ParseTree? = tree
fun parseTree(): ParseTreeNode? = tree

fun ruleNames(): Array<String> = ruleNames

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AntlrInterpreter(grammar: String, text: String): AntlrGrammarParser(gramma
val tokens = CommonTokenStream(lexEngine)
val parser = antlrGrammar!!.createParserInterpreter(tokens)
parser.addErrorListener(errorListener)
tree = parser.parse(antlrGrammar!!.getRule(0).index)
tree = convertParseTree(parser.parse(antlrGrammar!!.getRule(0).index), antlrGrammar?.ruleNames!!)
errors.addAll(errorListener.errors)
} else {
val tokens = convertTokens(lexEngine, lexEngine.allTokens)
Expand Down
Loading

0 comments on commit cbbae92

Please sign in to comment.