From: Thomas Walker Lynch
Date: Fri, 27 Sep 2024 11:09:36 +0000 (+0000)
Subject: includes DAG cycle finder, and rough drafts of the rest of the depdency graph build
X-Git-Url: https://git.reasoningtechnology.com/usr/lib/python2.7/encodings/cp855.py?a=commitdiff_plain;h=26260703d1ef5c7dd72fa81c02cd6f0d276e7367;p=GQL-to-Cypher
includes DAG cycle finder, and rough drafts of the rest of the depdency graph build
---
diff --git a/build.gradle b/build.gradle
index ec3a5bb..4312062 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,58 +1,94 @@
-defaultTasks 'installTools'
+defaultTasks 'installTools', ':developer:preface'
-/*--------------------------------------------------------------------------------
-
- Configuration variables
+//--------------------------------------------------------------------------------
+// Project manager should set these configuration variables
+//
+// tool versions
+def antlrVersion = '4.11.1'
+def jdkVersion = '22.0.1'
+def jdkBuild = '8'
-*/
+// The urls for upstream files
+def antlrUrl = "https://www.antlr.org/download/antlr-${antlrVersion}-complete.jar"
+def jdkUrl = "https://github.com/adoptium/temurin22-binaries/releases/download/jdk-${jdkVersion}%2B${jdkBuild}/OpenJDK22U-jdk_x64_linux_hotspot_${jdkVersion}_${jdkBuild}.tar.gz"
+//--------------------------------------------------------------------------------
// project structure
+//
def repoHome = rootDir
def toolDir = "${repoHome}/tool"
def upstreamDir = "${toolDir}/upstream"
def executorDir = "${toolDir}/executor"
def erebusDir = file("${rootDir}/Erebus")
-// tool versions
-def antlrVersion = '4.11.1'
-def jdkVersion = '22.0.1'
-def jdkBuild = '8'
+//--------------------------------------------------------------------------------
+// Configuration phase code
+//
+// Check that we are in the correct environment
+def repoHomeVar = System.getenv('REPO_HOME')
+def projectVar = System.getenv('PROJECT')
+if(
+ !repoHomeVar
+ || !projectVar
+){
+ println "REPO_HOME or PROJECT not set, has 'env_pm' or 'env_dev' been sourced?"
+ throw new GradleException("Bailing due to not being able to make sense of the environment.")
+}
+if(
+ projectVar != "GQL_to_Cypher"
+ && projectVar != "GQL_to_Cypher_PM"
+){
+ println "Expected project 'GQL_to_Cypher', or 'GQL_to_Cypher_PM' but found '${projectVar}'."
+ throw new GradleException("Bailing due to apparently being in the wrong project.")
+}
-// The urls for upstream files
-def antlrUrl = "https://www.antlr.org/download/antlr-${antlrVersion}-complete.jar"
-def jdkUrl = "https://github.com/adoptium/temurin22-binaries/releases/download/jdk-${jdkVersion}%2B${jdkBuild}/OpenJDK22U-jdk_x64_linux_hotspot_${jdkVersion}_${jdkBuild}.tar.gz"
+//--------------------------------------------------------------------------------
+// build
+//
+
+task preface {
+ doLast {
+ println "================================================================================"
+ println "installing tools .."
+ }
+}
task installAntlr {
+ dependsOn preface
doLast {
- def antlrJar = file("${upstreamDir}/antlr-${antlrVersion}-complete.jar")
- def antlrExecutorJar = file("${executorDir}/antlr-${antlrVersion}-complete.jar")
+ def antlrJar_FN = "antlr-${antlrVersion}-complete.jar"
+ def antlrJar_FP = file("${upstreamDir}/${antlrJar_FN}")
+ def antlrExecutorJar_FP = file("${executorDir}/${antlrJar_FN}")
mkdir(upstreamDir)
mkdir(executorDir)
- if (!antlrJar.exists()) {
+ if (!antlrJar_FP.exists()) {
println "Downloading ANTLR..."
- new URL(antlrUrl).withInputStream { i -> antlrJar.withOutputStream { it << i } }
+ new URL(antlrUrl).withInputStream { i -> antlrJar_FP.withOutputStream { it << i } }
}
- if (!antlrExecutorJar.exists()) {
+ if (!antlrExecutorJar_FP.exists()) {
copy {
- from antlrJar
+ from antlrJar_FP
into executorDir
}
println "ANTLR installed in executor."
} else {
println "ANTLR already installed."
}
+ project.ext.ANTLRjar = antlrExecutorJar_FP.absolutePath
+
}
}
task installJava {
+ dependsOn preface
doLast {
def jdkTar = file("${upstreamDir}/jdk-${jdkVersion}.tar.gz")
def jdkDir = file("${toolDir}/jdk-${jdkVersion}+${jdkBuild}")
-
+
mkdir(upstreamDir)
mkdir(toolDir)
@@ -77,7 +113,7 @@ task installJava {
if (!javaHome.exists()) {
throw new GradleException("JDK extraction failed.")
}
-
+
// variables saved for use in subproject build scripts
project.ext.javaHome = jdkDir
@@ -90,14 +126,9 @@ task installTools {
dependsOn installAntlr, installJava
}
-// Ensure all subprojects depend on the tool installation
-subprojects {
- afterEvaluate { project ->
- project.tasks.each { task ->
- task.dependsOn ':installTools'
- }
- }
-}
+//--------------------------------------------------------------------------------
+// additional tool install targets
+//
task cleanTool {
description = 'Cleans the installed tools (but not upstream files)'
@@ -123,7 +154,6 @@ task cleanTool {
task cleanErebus {
description = "Cleans the project level temporary directory 'Erebus'"
-
doLast {
if (erebusDir.exists()) {
erebusDir.eachFile { file ->
diff --git a/developer/#build.gradle# b/developer/#build.gradle#
new file mode 100644
index 0000000..35450ad
--- /dev/null
+++ b/developer/#build.gradle#
@@ -0,0 +1,696 @@
+//---------------------------------------------------------------------------------
+// globals
+
+// string comprehension includes for regular expressions
+def base = "[a-zA-Z0-9_-]+"
+def ext = "[a-zA-Z0-9_-]+$"
+def name = "${base}\\.${ext}"
+def path = ".+/${name}"
+
+//--------------------------------------------------------------------------------
+// Import variables from the environment
+//
+
+def env = [:]
+
+// Required shell environment variables
+def varName_List = [
+ 'REPO_HOME',
+ 'PROJECT',
+ 'ENV_BUILD_VERSION',
+ 'DEVELOPER_HOME'
+]
+varName_List.each { varName ->
+ def value = System.getenv(varName) ?: ""
+ env[varName] = value
+}
+
+// Optional shell environment variables
+def varNameOptional_List = [
+ 'CLASSPATH'
+]
+varNameOptional_List.each { varName ->
+ def value = System.getenv(varName) ?: ""
+ env[varName] = value
+}
+
+env.CLASSPATH += ":${env.ANTLR_JAR}"
+
+//--------------------------------------------------------------------------------
+// PM installed tools to be used
+// these should be added to the project object by the installer, and taken
+// from the project object here.
+//
+// Tools used (set by project manager: JAVA_HOME, ANTLR_JAR, DEVELOPER_HOME)
+def JAVA_COMP_FP = "${env.JAVA_HOME}/bin/javac" // Java compiler path
+def JAVA_INTERP_FP = "${env.JAVA_HOME}/bin/java" // Java interpreter path
+def JAVA_ARCHIVE_FP = "${env.JAVA_HOME}/bin/jar" // Java archive tool path
+
+
+//--------------------------------------------------------------------------------
+// Directory structure
+//
+
+def dir_map = [
+ ,'EXECUTOR_IN' : 'executor/'
+ ,'ANTLR_IN_LEAF' : 'ANTLR/'
+ ,'ANTLR_OUT' : 'javac/ANTLR/'
+ ,'ANTLR_OUT_PARENT' : 'javac/'
+ ,'JAVA_COMP_IN' : 'javac/'
+ ,'JAVA_COMP_IN_LEAF': 'javac/leaf/'
+ ,'JAVA_COMP_IN_ANTLR' : 'javac/ANTLR/'
+ ,'JAVA_COMP_IN_SYN' : 'javac/synthesized/'
+ ,'JAVA_COMP_OUT' : 'jvm/'
+ ,'JVM_IN' : 'jvm/'
+ ,'TEMP' : 'Erebus/'
+]
+
+// Update CLASSPATH
+env.CLASSPATH += ":${dir_map.JVM_IN}"
+
+// Construct JAVA_COMP_IN_LIST dynamically and add to dp map
+dir_map.JAVA_COMP_IN_LIST =
+ "${dir_map.JAVA_COMP_IN_LEAF}" +
+ ":${dir_map.JAVA_COMP_IN_ANTLR}" +
+ ":${dir_map.JAVA_COMP_IN_SYN}"
+
+println "CLASSPATH: ${env.CLASSPATH}"
+println "JAVA_COMP_IN_LIST: ${dir_map.JAVA_COMP_IN_LIST}"
+
+
+// Subroutine to print missing environment variables list message
+def printMissingVars(missingVars) {
+ if (missingVars.isEmpty()) {
+ // Print nothing if the list is empty
+ return
+ } else if (missingVars.size() == 1) {
+ println "This environment variable was not set: ${missingVars[0]}"
+ } else {
+ println "These environment variables were not set: ${missingVars.join(', ')}"
+ }
+}
+
+task preface {
+ dependsOn ':installTools'
+ doFirst {
+
+ // Environment variable checks
+ def error_missing = false
+ def error_project = false
+ def missingVars = [] // To collect missing environment variables
+ varName_List.each { varName ->
+ if (!env[varName]) {
+ error_missing = true
+ missingVars << varName
+ }
+ }
+ printMissingVars(missingVars)
+ if (env.PROJECT != "GQL_to_Cypher") {
+ error_project = true
+ println "Expected project 'GQL_to_Cypher', but found '${env.PROJECT}'."
+ }
+ if (error_missing || error_project) {
+ throw new GradleException("Bailing due to missing environment variables.")
+ }
+ }
+ doLast {
+ println "================================================================================"
+ println "Building project .."
+ }
+}
+
+/*--------------------------------------------------------------------------------
+ Dependency Graph
+
+ A node is a property list.
+
+ A node label is a relative path to a file, or a symbolic value.
+
+ We either a) put nodes in a map that key on the node label then return the node,
+ or b) we use functions that recognize the node label and return a property list.
+ The `lookup(node-label)` function combines all methods to find the node.
+
+ A will formed node will have these properties: label and type.
+
+ A node without a 'neighbor' property is said to be a 'leaf_node'.
+
+ `path` type nodes have a `build` property.
+*/
+
+//---------------------------------------
+// some helpers
+
+def node_type_set = ['symbol' ,'path'] as Set
+def node_mark_set = ['cycle_member' ,'malformed' ] as Set
+
+/*
+ */
+
+def well_formed_q = { node ->
+ def form_error_set = [] as Set
+
+ if(
+ !node.label || node.label.length() == 0
+ )
+ form_error_set << 'no_node_label'
+
+ if(
+ !node.type || !(node.type in node_type_set)
+ )
+ form_error_set << 'no_node_type'
+
+ if(
+ node.neighbor
+ && !(node.neighbor instanceof List)
+ )
+ form_error_set << 'neighbor_not_a_list'
+
+ if(
+ node.type == "path"
+ && (!node.build || !(node.build instanceof Closure))
+ )
+ form_error_set << 'no_build_code'
+
+ if(
+ node.type == "symbol"
+ && node.build
+ )
+ form_error_set << 'symbol_has_build_code'
+
+ if(
+ node.mark
+ && (node.mark instance of Set)
+ && ! (node.mark.every {it in node_mark_set})
+ )
+ form_error_set << 'unregistered_mark'
+
+ return form_error_set
+}
+def is_leaf_q = { node -> !node.neighbor || node.neighbor.isEmpty() }
+def is_marked_q = { node -> node.mark && !node.mark.isEmpty() }
+
+
+/*----------------------------------------
+ maps node label -> node
+
+ The keys are the node labels. They are formally added as the value to the
+ `label` property by the lookup function.
+
+ Each leaf node must have a node definition. All path labels that lead to the
+ `javac/leaf` directory are recognized as leaf nodes. A leaf node has a
+ `type` property value of `leaf`, and they can be added to the map at different
+ file paths.
+
+*/
+
+def node_map = [
+
+ "ANTLR_OUT_FL" : [
+ ,type: "symbol"
+ ,neighbor: ["${dir_map.EXECUTOR_IN}/ANTLR_OUT_FL"]
+ ]
+
+ ,"RuleNameList" : [
+ ,type: "symbol"
+ ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameList"]
+ ]
+
+ ,"RuleNameListRegx" : [
+ ,type: "symbol"
+ ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameListRegx"]
+ ]
+
+ ,"Synthesize_SyntaxAnnotate" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java"
+ ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate"
+ ]
+ ]
+
+ ,"Synthesize_SyntaxAnnotate_PrintVisitor" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java"
+ ,"${dir_map.JAVA_COMP_IN_LEAF}/Synthesize_SyntaxAnnotate_PrintVisitorMethod.java"
+ ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitor"
+ ]
+ ]
+
+ ,"Synthesize_SyntaxAnnotate_PrintVisitorMethod" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java"
+ ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitorMethod"
+ ]
+ ]
+]
+
+//----------------------------------------
+// the following are recognizer functions that return node definitions.
+
+// any leaf node located in javac/leaf
+def node_leaf_f(node_label) {
+ def leafNodePattern = ~/${dir_map['JAVA_COMP_IN_LEAF']}(.*)/
+ def match = node_label =~ leafNodePattern
+ if (!match) {
+ return [status: "no_match"]
+ }
+ def baseName = match[0][1]
+
+ def leafFilePath = "${dir_map['JAVA_COMP_IN_LEAF']}${baseName}"
+ def leafFile = new File(leafFilePath)
+ if (!leafFile.exists()) {
+ return [status: "no_match"]
+ }
+
+ return [
+ status: "matched"
+ ,label: node_label
+ ,type: "leaf"
+ ,neighbor: []
+ ]
+}
+
+// any shell wrapper (for wrapping a name corresponding .jar file)
+def node_executor_f(node) {
+
+ def match = node =~ /^(executor\/)(${base})$/
+ if (!match) {
+ return [status: "no_match"]
+ }
+ def baseName = match[0][2]
+
+ def jarFilePath = "${dir_map['JVM_IN']}${baseName}.jar"
+ def wrapperFilePath = "${dir_map['EXECUTOR_IN']}${baseName}"
+
+ def jarFile = new File(jarFilePath)
+ if (!jarFile.exists()) {
+ return [status: "no_match"]
+ }
+
+ return [
+ status: "matched"
+ ,label: node
+ ,type: "path"
+ ,neighbor: [jarFilePath]
+ ,build: { node, neighbor ->
+
+ // The script for wrapping the jar file:
+ def wrapper = """
+ #!/usr/bin/env bash
+ ${dir_map['JAVA_INTERP']} -cp \${CLASSPATH}:${dir_map['JVM_IN']}:${dir_map['JVM_IN']}/${baseName}.jar ${baseName} \\\$\\@
+ """
+
+ new File(wrapperFilePath).withWriter('UTF-8') { writer ->
+ writer.write(wrapper)
+ }
+
+ println "Creating executable wrapper script for ${baseName} in executor directory."
+ "chmod +x ${wrapperFilePath}".execute().text
+ }
+ ]
+}
+
+// any antlr output java file
+def node_grammar_f(node) {
+
+ def match = node =~ /^(${dir_map['ANTLR_OUT']})(${base})(Lexer|Parser|BaseListener|Listener|BaseVisitor|Visitor)\.java$/
+ if( !match ){
+ return [status: "no_match"]
+ }
+
+ def grammarName = match[0][2]
+ def outputType = match[0][3]
+
+ def grammarFilePath = "${dir_map['ANTLR_IN_LEAF']}${grammarName}.g4"
+ def grammarFile = new File(grammarFilePath)
+
+ if( !grammarFile.exists() ){
+ return [status: "no_match"]
+ }
+
+ return [
+ status: "matched"
+ ,label: node
+ ,type: "path"
+ ,neighbor: [grammarFilePath]
+ ]
+}
+
+// any class file (built from a name corresponding .java file)
+def node_class_f(node) {
+
+ def match = node =~ /^(${dir_map['JAVA_COMP_OUT']})(${base})\.class$/
+ if( !match ){
+ return [status: "no_match"]
+ }
+
+ def baseName = match[0][2]
+ def javaFilePath = "${dir_map['JAVA_COMP_IN_LEAF']}${baseName}.java"
+ def javaFile = new File(javaFilePath)
+
+ if( !javaFile.exists() ){
+ return [status: "no_match"]
+ }
+
+ return [
+ status: "matched"
+ ,label: node
+ ,type: "path"
+ ,neighbor: [javaFilePath]
+ ]
+}
+
+// any jar file (built from a corresponding class file)
+def node_jar_f(node) {
+
+ // Use the symbolic name and base patterns
+ def match = node =~ /^(${dir_map['JAVA_COMP_OUT']})(${base})\.jar$/
+
+ if( !match ){
+ return [status: "no_match"]
+ }
+
+ def baseName = match[0][2]
+ def classFilePath = "${dir_map['JAVA_COMP_OUT']}${baseName}.class"
+ def classFile = new File(classFilePath)
+
+ if( !classFile.exists() ){
+ return [status: "no_match"]
+ }
+
+ return [
+ status: "matched"
+ ,label: node
+ ,type: "path"
+ ,neighbor: [classFilePath]
+ ,build: { node, neighbor ->
+ println "Building jar for ${baseName}"
+ def command = "${ext.javaHome}/bin/jar cf ${baseName}.jar -C ${dir_map['JAVA_COMP_OUT']} ${baseName}.class"
+ return command.execute().text;
+ }
+ ]
+}
+
+// list of the recognizer functions
+def node_f_list = [
+ node_leaf_f
+ ,node)executor_f
+ ,node_grammar_f
+ ,node_class_f
+ ,node_jar_f
+]
+
+
+// combines the map and recognizer functions into a single lookup
+def lookup(node_label, verbose = false){
+ // the actual lookup
+ def lookup_node = node_map[node_label]
+ if( lookup_node ){
+ lookup_node.label = node_label
+ } else {
+ def match_result
+ for( func in node_f_list ){
+ match_result = func(node_label)
+ if( match_result.status == "matched" ){
+ lookup_node = match_result
+ break
+ }
+ }
+ }
+
+ if( !lookup_node ){
+ if( verbose ) println "Lookup error: Node ${node_label} could not be found."
+ return null
+ }
+
+ // If we already marked this one as malformed, do not check it again
+ // This is done primarily to avoid duplicate error messages, as it isn't a big
+ // performance win.
+ if( lookup_node.mark && ('malformed' in lookup_node.mark) ) return null
+
+ // Check if the node is well formed
+ def form_errors = well_formed_q(lookup_node)
+ if( !form_errors.isEmpty() ){
+ // Mark the node as malformed
+ if( !lookup_node.mark ) lookup_node.mark = [] as Set
+ lookup_node.mark << 'malformed'
+
+ // Tell the user
+ if( verbose ){
+ println "Lookup error: Node ${node_label} is malformed due to: ${form_errors.join(', ')}"
+ }
+
+ // Refuse to return a malformed node
+ return null
+ }
+
+ return lookup_node
+}
+
+/*--------------------------------------------------------------------------------
+ is_acyclic_q checks a dependency graph for cycles
+
+ given a dependency graph. Returns either 'given_argument_not_accepted',
+ `cycles` or `acyclic`.
+
+ marks the nodes that are in cycles.
+
+ There is a call function and a helper function
+
+*/
+
+def is_acyclic_q_descend(path_stack ,boolean verbose = true){
+ def local_path = path_stack.collect{ node_label -> lookup(node_label) }
+ def local_node = local_path[-1]
+ def cycle_start_index
+
+ do{
+ // Check for a cycle in the local path
+ cycle_start_index = local_path[0..-2].findIndexOf{ node -> node.label == local_node.label }
+ if( cycle_start_index != -1 ){ // Cycle detected
+ if( verbose ) print "is_acyclic_q:: dependency cycle found:"
+ local_path[cycle_start_index..-1].each{ cycle_node ->
+ if( verbose ) print " ${cycle_node.label}"
+ cycle_node.mark = cycle_node.mark ?: [] as Set // Initialize mark set if needed
+ cycle_node.mark << 'cycle_member'
+ }
+ if( verbose ) println ""
+ // Pop off the stack above the cycle start, so that search can continue if desired
+ path_stack = path_stack[0..cycle_start_index]
+ return 'cycle_found'
+ }
+
+ // Put this test after the cycle detector in case another definition with
+ // the same label had children and participated in a cycle.
+ if( local_node.neighbor.isEmpty() ) return 'leaf_node'
+
+ // Descend further into the tree.
+ path_stack << local_node.neighbor.clone()
+ local_node = lookup(local_node.neighbor[0])
+ local_path << local_node
+ }while( true )
+}
+
+def is_acyclic_q(root_node_labels ,boolean verbose = true){
+ // We are optimistic.
+ def return_value = 'acyclic'
+
+ // Initialize the DFS tree iterator.
+ def path_stack = []
+ path_stack << root_node_labels.clone()
+
+ // iterate over left side tree descent, not ideal as it starts at the
+ // root each time, but avoids complexity in the cycle detection logic.
+ do{
+ def result = is_acyclic_q_descend(path_stack ,verbose)
+ if(result == 'cycle_found') return_value = result
+
+ // increment the iterator to the next leftmost path
+ def top_list = path_stack[-1]
+ top_list.remove(0)
+ if( top_list.isEmpty() ) path_stack.pop()
+
+ }while( !path_stack.isEmpty() )
+
+ return return_value
+}
+
+// LocalWords: FN FPL DN DNL RuleNameListRegx RuleNameList PrintVisitorMethod
+// LocalWords: PrintVisitor SyntaxAnnotate
+
+/*--------------------------------------------------------------------------------
+ run the build scripts
+ depends upon is_acyclic having already marked up the graph.
+
+Initialization:
+* visited: A set to keep track of visited nodes.
+* build_order: A list to store the nodes in the order they should be built.
+
+Depth-First Search (DFS):
+* The dfs function performs a depth-first traversal of the graph.
+* It looks up the node using the lookup function.
+* If the node has already been visited, it returns.
+* Otherwise, it marks the node as visited and recursively visits its neighbors.
+* After visiting all neighbors, it adds the node to the build_order list.
+
+Build Script Execution:
+* After the DFS traversal, the build_order list is reversed to ensure that leaf nodes are built first.
+* The build scripts for each node are executed in the correct order.
+
+-- add date checks
+
+*/
+
+def run_build_scripts(root_node_labels ,boolean verbose = false){
+ def visited = [] as Set
+ def build_order = []
+
+ def dfs(node_label){
+ def node = lookup(node_label ,verbose)
+ if( !node ) return
+
+ if( node.label in visited ) return
+ visited << node.label
+
+ node.neighbor.each{ neighbor_label ->
+ dfs(neighbor_label)
+ }
+
+ build_order << node
+ }
+
+ root_node_labels.each{ root_label ->
+ dfs(root_label)
+ }
+
+ build_order.reverse().each{ node ->
+ if( node.build ){
+ println "Running build script for ${node.label}"
+ node.build(node ,node.neighbor)
+ }
+ }
+}
+
+// Example usage
+def root_node_labels = ["ANTLR_OUT_FL" ,"RuleNameList"]
+run_build_scripts(root_node_labels ,true)
+
+
+def run_build_scripts(root_node_labels ,boolean verbose = false){
+ if( root_node_labels.isEmpty() ) return
+
+ def visited = [] as Set
+ def build_order = []
+ def stack = []
+
+ root_node_labels.each{ root_label ->
+ stack << root_label
+ }
+
+ do{
+ def node_label = stack.pop()
+ def node = lookup(node_label ,verbose)
+ if( !node ) continue
+
+ if( node.label in visited ) continue
+ visited << node.label
+
+ node.neighbor.each{ neighbor_label ->
+ stack << neighbor_label
+ }
+
+ build_order << node
+ }while( !stack.isEmpty() )
+
+ build_order.reverse().each{ node ->
+ if( node.build ){
+ println "Running build script for ${node.label}"
+ node.build(node ,node.neighbor)
+ }
+ }
+}
+
+--------------------
+
+def should_build(node, verbose = false){
+ if( node.mark && ('malformed' in node.mark || 'cycle_member' in node.mark) ){
+ if( verbose ) println "Skipping build for ${node.label} due to errors or cycle membership."
+ return false
+ }
+
+ def nodeFile = new File(node.label)
+ if( !nodeFile.exists() ){
+ if( verbose ) println "Node file ${node.label} does not exist."
+ return false
+ }
+
+ def nodeLastModified = nodeFile.lastModified()
+ def shouldBuild = false
+
+ node.neighbor.each{ neighbor_label ->
+ def neighborNode = lookup(neighbor_label, verbose)
+ if( !neighborNode || neighborNode.type != 'path' ){
+ if( verbose ) println "Dependency ${neighbor_label} is missing or not a path type."
+ return false
+ }
+
+ def neighborFile = new File(neighbor_label)
+ if( !neighborFile.exists() ){
+ if( verbose ) println "Dependency file ${neighbor_label} does not exist."
+ return false
+ }
+
+ def neighborLastModified = neighborFile.lastModified()
+ if( neighborLastModified > nodeLastModified ){
+ shouldBuild = true
+ }
+ }
+
+ return shouldBuild
+}
+
+def run_build_scripts(root_node_labels ,boolean verbose = false){
+ if( root_node_labels.isEmpty() ) return
+
+ def visited = [] as Set
+ def build_order = []
+ def stack = []
+
+ root_node_labels.each{ root_label ->
+ stack << root_label
+ }
+
+ do{
+ def node_label = stack.pop()
+ def node = lookup(node_label ,verbose)
+ if( !node ) continue
+
+ if( node.label in visited ) continue
+ visited << node.label
+
+ node.neighbor.each{ neighbor_label ->
+ stack << neighbor_label
+ }
+
+ build_order << node
+ }while( !stack.isEmpty() )
+
+ build_order.reverse().each{ node ->
+ if( node.build && should_build(node ,verbose) ){
+ println "Running build script for ${node.label}"
+ node.build(node ,node.neighbor)
+ }
+ }
+}
+
+// Example usage
+def root_node_labels = ["ANTLR_OUT_FL" ,"RuleNameList"]
+run_build_scripts(root_node_labels ,true)
+
+
+
+// Example usage
+def root_node_labels = ["ANTLR_OUT_FL" ,"RuleNameList"]
+run_build_scripts(root_node_labels ,true)
diff --git a/developer/Lethe/buid_from_leaf.gradle b/developer/Lethe/buid_from_leaf.gradle
new file mode 100644
index 0000000..c23a159
--- /dev/null
+++ b/developer/Lethe/buid_from_leaf.gradle
@@ -0,0 +1,68 @@
+/*--------------------------------------------------------------------------------
+ build_from_leaf
+
+ Accepts `build_result_fpl` and `leaf_dependency_fpl` (fpl = file path list).
+ Typically, `build_result_fpl` will hold a single item, representing the target
+ to be built, and `leaf_dependency_fpl` contains the leaf dependencies for that
+ target.
+
+ This function checks the build status of the target relative to its dependencies.
+
+ It returns a dictionary with the following fields based on the result of the check:
+
+ type -> A string indicating the result type:
+ 'no_build_target_specified', 'non_existent_build_target',
+ 'up_to_date', or 'need_to_build'.
+
+ message -> A string message describing the result (optional).
+
+ missing -> If type is 'non_existent_build_target', this field contains the
+ list of missing build targets.
+
+ build_result -> If type is 'need_to_build', this field contains the list
+ of build result filenames that need to be rebuilt.
+*/
+
+def build_from_leaf(build_result_fpl ,leaf_dependency_fpl) {
+
+ if( !build_result_fpl || build_result_fpl.isEmpty() ){
+ return [
+ type: "no_build_target_specified"
+ ,message: "No build target specified"
+ ]
+ }
+
+ // Check if any build result file does not exist
+ def missing_build_fpl = build_result_fpl.findAll{ file_path ->
+ !new File(file_path).exists()
+ }
+
+ if( !missing_build_fpl.isEmpty() ){
+ return [
+ type: "non_existent_build_target"
+ ,message: "Missing build targets"
+ ,missing: missing_build_fpl
+ ]
+ }
+
+ // Check if any dependency is newer than the build result files
+ def all_up_to_date_bool = build_result_fpl.every{ result_file_path ->
+ def result_file = new File(result_file_path)
+ !leaf_dependency_fpl.any{ dep_path ->
+ new File(dep_path).lastModified() > result_file.lastModified()
+ }
+ }
+
+ if( all_up_to_date_bool ){
+ return [
+ type: "up_to_date"
+ ,message: "Build result is up to date"
+ ]
+ }else{
+ return [
+ type: "need_to_build"
+ ,message: "Build result needs to be rebuilt"
+ ,build_result: build_result_fpl
+ ]
+ }
+}
diff --git a/developer/Lethe/build.gradle b/developer/Lethe/build.gradle
index b5ab4a6..e53add0 100644
--- a/developer/Lethe/build.gradle
+++ b/developer/Lethe/build.gradle
@@ -1,8 +1,23 @@
task setup {
doLast {
- def dirs = ["$(ANTLR_IN_PRIMARY_DIR)", "$(JAVA_COMP_IN_PRIMARY_DIR)", "$(JVM_IN_DIR)", "$(EXECUTOR_IN_DIR)", "test", "deprecated", "experiment", "ologist", "temporary"]
+ def dirs = [
+ "$(ANTLR_IN_PRIMARY_DIR)"
+ ,"$(JAVA_COMP_IN_DIR)"
+ ,"$(JAVA_COMP_IN_PRIMARY_DIR)"
+ ,"$(JAVA_COMP_IN_ANTLR_DIR)"
+ ,"$(JAVA_COMP_IN_SYN_DIR)"
+ ,"$(JVM_IN_DIR)"
+ ,"$(EXECUTOR_IN_DIR)"
+ ,"test"
+ ,"deprecated"
+ ,"experiment"
+ ,"ologist"
+ ,"temporary"
+ ]
dirs.each { dir ->
- mkdir dir
+ if (!file(dir).exists()) {
+ mkdir dir
+ }
}
}
}
@@ -54,38 +69,3 @@ tasks.withType(Exec) {
println "Executing $name"
}
}
-
-
-----------------------
-// Function to compile .java files to .class files
-def compileJava(source, target) {
- tasks.create(name: "compile${source}", type: Exec) {
- commandLine '$(JAVA_COMP)', '-d', '$(JAVA_COMP_OUT_DIR)', '-sourcepath', '$(JAVA_COMP_IN_DL)', source
- doLast {
- println "Compiled ${source} to ${target}"
- }
- }
-}
-
-// Function to create .jar files from .class files
-def createJar(source, target) {
- tasks.create(name: "jar${source}", type: Exec) {
- commandLine '$(JAVA_ARCHIVE)', 'cf', target, '-C', '$(JAVA_COMP_OUT_DIR)', source
- doLast {
- println "Created ${target}"
- }
- }
-}
-
-// Function to create wrapper scripts from .jar files
-def createWrapper(source, target) {
- tasks.create(name: "wrapper${source}", type: Exec) {
- doLast {
- def script = new File(target)
- script.text = "#!/usr/bin/env bash\n$(JAVA_INTERP) -cp ${CLASSPATH}:${JVM_IN_DP}:${JVM_IN_DP}/${source}.jar ${source} \$@"
- script.setExecutable(true)
- println "Created program ${target}"
- }
- }
-}
-
diff --git a/developer/Lethe/makefile-project.mk b/developer/Lethe/makefile-project.mk
index ea8a0f3..7f8d6ef 100644
--- a/developer/Lethe/makefile-project.mk
+++ b/developer/Lethe/makefile-project.mk
@@ -46,7 +46,7 @@ Synthesize_SyntaxAnnotate_PrintVisitor:\
$(EXECUTOR_IN_DIR)/Synthesize_SyntaxAnnotate_PrintVisitor
Synthesize_SyntaxAnnotate:\
- $(JAVA_COMP_IN_PRIMARY_DIR)/StringUtils.java\
+ $(JAVA_COMP_IN_PRIMARY_DIR)/StringUtils.java\https://github.com/Thomas-Walker-Lynch/GQL_to_Cypher
$(EXECUTOR_IN_DIR)/Synthesize_SyntaxAnnotate
#-----------------------------------------------
@@ -236,6 +236,7 @@ $(JAVA_COMP_OUT_DIR)/%.class: $(JAVA_COMP_IN_PRIMARY_DIR)/%.java
.PRECIOUS: $(JAVA_COMP_OUT_DIR)/%.jar
# make .jar from .class file
+$(JAVA_COMP_OUT_DIR)/%.class: $(JAVA_COMP_IN_PRIMARY_DIR)/%.java
$(JAVA_COMP_OUT_DIR)/%.jar: $(JAVA_COMP_OUT_DIR)/%.class
@echo "Building $*..."
$(JAVA_ARCHIVE) cf $@ -C $(JAVA_COMP_OUT_DIR) $*.class
diff --git a/developer/build.gradle b/developer/build.gradle
index e53add0..ffc383d 100644
--- a/developer/build.gradle
+++ b/developer/build.gradle
@@ -1,71 +1,617 @@
-task setup {
- doLast {
- def dirs = [
- "$(ANTLR_IN_PRIMARY_DIR)"
- ,"$(JAVA_COMP_IN_DIR)"
- ,"$(JAVA_COMP_IN_PRIMARY_DIR)"
- ,"$(JAVA_COMP_IN_ANTLR_DIR)"
- ,"$(JAVA_COMP_IN_SYN_DIR)"
- ,"$(JVM_IN_DIR)"
- ,"$(EXECUTOR_IN_DIR)"
- ,"test"
- ,"deprecated"
- ,"experiment"
- ,"ologist"
- ,"temporary"
- ]
- dirs.each { dir ->
- if (!file(dir).exists()) {
- mkdir dir
+//---------------------------------------------------------------------------------
+// globals
+
+// string comprehension includes for regular expressions
+def base = "[a-zA-Z0-9_-]+"
+def ext = "[a-zA-Z0-9_-]+$"
+def name = "${base}\\.${ext}"
+def path = ".+/${name}"
+
+//--------------------------------------------------------------------------------
+// Import variables from the environment
+//
+
+def env = [:]
+
+// Required shell environment variables
+def varName_List = [
+ 'REPO_HOME',
+ 'PROJECT',
+ 'ENV_BUILD_VERSION',
+ 'DEVELOPER_HOME'
+]
+varName_List.each { varName ->
+ def value = System.getenv(varName) ?: ""
+ env[varName] = value
+}
+
+// Optional shell environment variables
+def varNameOptional_List = [
+ 'CLASSPATH'
+]
+varNameOptional_List.each { varName ->
+ def value = System.getenv(varName) ?: ""
+ env[varName] = value
+}
+
+env.CLASSPATH += ":${env.ANTLR_JAR}"
+
+//--------------------------------------------------------------------------------
+// PM installed tools to be used
+// these should be added to the project object by the installer, and taken
+// from the project object here.
+//
+// Tools used (set by project manager: JAVA_HOME, ANTLR_JAR, DEVELOPER_HOME)
+def JAVA_COMP_FP = "${env.JAVA_HOME}/bin/javac" // Java compiler path
+def JAVA_INTERP_FP = "${env.JAVA_HOME}/bin/java" // Java interpreter path
+def JAVA_ARCHIVE_FP = "${env.JAVA_HOME}/bin/jar" // Java archive tool path
+
+
+//--------------------------------------------------------------------------------
+// Directory structure
+//
+
+def dir_map = [
+ ,'EXECUTOR_IN' : 'executor/'
+ ,'ANTLR_IN_LEAF' : 'ANTLR/'
+ ,'ANTLR_OUT' : 'javac/ANTLR/'
+ ,'ANTLR_OUT_PARENT' : 'javac/'
+ ,'JAVA_COMP_IN' : 'javac/'
+ ,'JAVA_COMP_IN_LEAF': 'javac/leaf/'
+ ,'JAVA_COMP_IN_ANTLR' : 'javac/ANTLR/'
+ ,'JAVA_COMP_IN_SYN' : 'javac/synthesized/'
+ ,'JAVA_COMP_OUT' : 'jvm/'
+ ,'JVM_IN' : 'jvm/'
+ ,'TEMP' : 'Erebus/'
+]
+
+// Update CLASSPATH
+env.CLASSPATH += ":${dir_map.JVM_IN}"
+
+// Construct JAVA_COMP_IN_LIST dynamically and add to dp map
+dir_map.JAVA_COMP_IN_LIST =
+ "${dir_map.JAVA_COMP_IN_LEAF}" +
+ ":${dir_map.JAVA_COMP_IN_ANTLR}" +
+ ":${dir_map.JAVA_COMP_IN_SYN}"
+
+println "CLASSPATH: ${env.CLASSPATH}"
+println "JAVA_COMP_IN_LIST: ${dir_map.JAVA_COMP_IN_LIST}"
+
+
+// Subroutine to print missing environment variables list message
+def printMissingVars(missingVars) {
+ if (missingVars.isEmpty()) {
+ // Print nothing if the list is empty
+ return
+ } else if (missingVars.size() == 1) {
+ println "This environment variable was not set: ${missingVars[0]}"
+ } else {
+ println "These environment variables were not set: ${missingVars.join(', ')}"
+ }
+}
+
+task preface {
+ dependsOn ':installTools'
+ doFirst {
+
+ // Environment variable checks
+ def error_missing = false
+ def error_project = false
+ def missingVars = [] // To collect missing environment variables
+ varName_List.each { varName ->
+ if (!env[varName]) {
+ error_missing = true
+ missingVars << varName
}
}
+ printMissingVars(missingVars)
+ if (env.PROJECT != "GQL_to_Cypher") {
+ error_project = true
+ println "Expected project 'GQL_to_Cypher', but found '${env.PROJECT}'."
+ }
+ if (error_missing || error_project) {
+ throw new GradleException("Bailing due to missing environment variables.")
+ }
+ }
+ doLast {
+ println "================================================================================"
+ println "Building project .."
}
}
-task tool(type: Exec) {
- dependsOn setup
- commandLine '$(BIN_MAKE)', '-f', '$(EXECUTOR_IN_DIR)/makefile-tool.mk', '-$(MAKEFLAGS)', 'all'
+/*--------------------------------------------------------------------------------
+ Dependency Graph
+
+ A node is a property list.
+
+ A node label is a relative path to a file, or a symbolic value.
+
+ We either a) put nodes in a map that key on the node label then return the node,
+ or b) we use functions that recognize the node label and return a property list.
+ The `lookup(node-label)` function combines all methods to find the node.
+
+ A will formed node will have these properties: label and type.
+
+ A node without a 'neighbor' property is said to be a 'leaf_node'.
+
+ `path` type nodes have a `build` property.
+*/
+
+//---------------------------------------
+// some helpers
+
+def node_type_set = ['symbol' ,'path'] as Set
+def node_mark_set = ['cycle_member' ,'malformed' ] as Set
+
+/*
+ */
+
+def well_formed_q = { node ->
+ def form_error_set = [] as Set
+
+ if(
+ !node.label || node.label.length() == 0
+ )
+ form_error_set << 'no_node_label'
+
+ if(
+ !node.type || !(node.type in node_type_set)
+ )
+ form_error_set << 'no_node_type'
+
+ if(
+ node.neighbor
+ && !(node.neighbor instanceof List)
+ )
+ form_error_set << 'neighbor_not_a_list'
+
+ if(
+ node.type == "path"
+ && (!node.build || !(node.build instanceof Closure))
+ )
+ form_error_set << 'no_build_code'
+
+ if(
+ node.type == "symbol"
+ && node.build
+ )
+ form_error_set << 'symbol_has_build_code'
+
+ if(
+ node.mark
+ && (node.mark instance of Set)
+ && ! (node.mark.every {it in node_mark_set})
+ )
+ form_error_set << 'unregistered_mark'
+
+ return form_error_set
}
+def is_leaf_q = { node -> !node.neighbor || node.neighbor.isEmpty() }
+def is_marked_q = { node -> node.mark && !node.mark.isEmpty() }
+
+
+/*----------------------------------------
+ maps node label -> node
+
+ The keys are the node labels. They are formally added as the value to the
+ `label` property by the lookup function.
+
+ Each leaf node must have a node definition. All path labels that lead to the
+ `javac/leaf` directory are recognized as leaf nodes. A leaf node has a
+ `type` property value of `leaf`, and they can be added to the map at different
+ file paths.
+
+*/
+
+def node_map = [
+
+ "ANTLR_OUT_FL" : [
+ ,type: "symbol"
+ ,neighbor: ["${dir_map.EXECUTOR_IN}/ANTLR_OUT_FL"]
+ ]
+
+ ,"RuleNameList" : [
+ ,type: "symbol"
+ ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameList"]
+ ]
+
+ ,"RuleNameListRegx" : [
+ ,type: "symbol"
+ ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameListRegx"]
+ ]
+
+ ,"Synthesize_SyntaxAnnotate" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java"
+ ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate"
+ ]
+ ]
+
+ ,"Synthesize_SyntaxAnnotate_PrintVisitor" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java"
+ ,"${dir_map.JAVA_COMP_IN_LEAF}/Synthesize_SyntaxAnnotate_PrintVisitorMethod.java"
+ ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitor"
+ ]
+ ]
+
+ ,"Synthesize_SyntaxAnnotate_PrintVisitorMethod" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java"
+ ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitorMethod"
+ ]
+ ]
+]
+
+//----------------------------------------
+// the following are recognizer functions that return node definitions.
+
+// any leaf node located in javac/leaf
+def node_leaf_f(node_label) {
+ def leafNodePattern = ~/${dir_map['JAVA_COMP_IN_LEAF']}(.*)/
+ def match = node_label =~ leafNodePattern
+ if (!match) {
+ return [status: "no_match"]
+ }
+ def baseName = match[0][1]
+
+ def leafFilePath = "${dir_map['JAVA_COMP_IN_LEAF']}${baseName}"
+ def leafFile = new File(leafFilePath)
+ if (!leafFile.exists()) {
+ return [status: "no_match"]
+ }
-task project(type: Exec) {
- dependsOn tool
- commandLine '$(BIN_MAKE)', '-f', '$(EXECUTOR_IN_DIR)/makefile-project.mk', '-$(MAKEFLAGS)', 'all'
+ return [
+ status: "matched"
+ ,label: node_label
+ ,type: "leaf"
+ ,neighbor: []
+ ]
}
-task clean {
- doLast {
- println "Use the command `clean
+
+ Returns:
+
+
'acyclic' if no cycles are found.
+
'cycle_found' if cycles are detected.
+
+
+
+ Process:
+
+
Initializes a stack for DFS traversal.
+
Iteratively calls the is_acyclic_q_descend function to traverse the graph and detect cycles.
+
Updates the traversal state and continues exploring other paths until the stack is empty.
+
+
+
2. is_acyclic_q_descend
+
+ Purpose: To perform the actual DFS traversal and cycle detection for a given path.
+
+
+ Parameters:
+
+
path_stack: A stack representing the current path in the graph.
+
verbose: A boolean flag for enabling detailed output (default is true).
+
+
+
+ Returns:
+
+
'leaf_node' if the current node has no children.
+
'cycle_found' if a cycle is detected.
+
+
+
+ Process:
+
+
Collects the current path and node.
+
Checks for cycles by comparing the current node with nodes in the path.
+
Marks nodes involved in cycles and updates the stack to continue traversal.
+
+
+
Usage
+
+ The is_acyclic_q function is used to ensure that the dependency graph defined in the build file is free of cycles. This is crucial for preventing infinite loops and ensuring that the build process can proceed smoothly.
+
+ The is_acyclic_q function is designed to detect cycles in a dependency graph using a depth-first search (DFS) algorithm. It starts from a list of root node labels and traverses the graph to ensure that there are no cycles. If a cycle is detected, the function marks the nodes involved and continues to explore other parts of the graph.
+
+
Key Concepts
+
+
Dependency Graph: A graph where nodes represent build targets and edges represent dependencies between these targets.
+
Depth-First Search (DFS): An algorithm for traversing or searching tree or graph data structures. It starts at the root and explores as far as possible along each branch before backtracking.
+
Cycle Detection: The process of identifying cycles (loops) in a graph, where a cycle is a path that starts and ends at the same node.
+
+
Functions
+
1. is_acyclic_q
+
+ Purpose: To determine if the dependency graph is acyclic (i.e., contains no cycles).
+
+
+ Parameters:
+
+
root_node_labels: A list of labels for the root nodes to start the cycle search.
+
verbose: A boolean flag for enabling detailed output (default is true).
+
+
+
+ Returns:
+
+
'acyclic' if no cycles are found.
+
'cycle_found' if cycles are detected.
+
+
+
+ Process:
+
+
Initializes a stack for DFS traversal.
+
Iteratively calls the is_acyclic_q_descend function to traverse the graph and detect cycles.
+
Updates the traversal state and continues exploring other paths until the stack is empty.
+
+
+
2. is_acyclic_q_descend
+
+ Purpose: To perform the actual DFS traversal and cycle detection for a given path.
+
+
+ Parameters:
+
+
path_stack: A stack representing the current path in the graph.
+
verbose: A boolean flag for enabling detailed output (default is true).
+
+
+
+ Returns:
+
+
'leaf_node' if the current node has no children.
+
'cycle_found' if a cycle is detected.
+
+
+
+ Process:
+
+
Collects the current path and node.
+
Checks for cycles by comparing the current node with nodes in the path.
+
Marks nodes involved in cycles and updates the stack to continue traversal.
+
+
+
Usage
+
+ The is_acyclic_q function is used to ensure that the dependency graph defined in the build file is free of cycles. This is crucial for preventing infinite loops and ensuring that the build process can proceed smoothly.
+
+
+
+
+
+
2. Run Build Scripts
+
+
Traverse the queue starting from the tail (the most recently added nodes).
+
For each node, attempt to build it by checking the file dates of the target node and its dependencies:
+
+
If the target file is older than any dependency, execute the nodeâs build function.
+
After building, recheck the file dates. If the file date has been updated, mark the node as successfully built.
+
If the build fails or the file date is not updated, mark the node with an error in its property list.
+
+
+
Nodes with dependencies marked with errors will not be built, and errors will propagate up the dependency tree.
+
+
+
3. Final Reporting and Status
+
+
If the root node is successfully built, report the success and any other successfully built nodes.
+
If an error has propagated to the root, report the failure.
+
Keep a list of all successfully built nodes and provide a final summary of the build status.
+
+
+
4. Node Definitions
+
Each node in the dependency graph is defined by a property dictionary. A node is either a symbol or a path:
+
+
Symbol Nodes: These represent abstract concepts or commands and always trigger a build unless marked with an error.
+
Path Nodes: These represent file paths. A path node is considered built if its target file is newer than its dependencies.
+
+
Both node types are identified by a label, and their dependencies are stored as a list of node labels. The presence of an error property indicates that the node has failed to build or encountered a problem during processing.