From: Thomas Walker Lynch
Date: Fri, 4 Oct 2024 08:46:17 +0000 (+0000)
Subject: more code from from the GQL_to_Cypher build script
X-Git-Url: https://git.reasoningtechnology.com/style/static/gitweb.css?a=commitdiff_plain;h=93fe2e47937b668166396e155821c7d450e3a590;p=Ariadne
more code from from the GQL_to_Cypher build script
---
diff --git a/developer/deprecated/#Ariandne.groovy# b/developer/deprecated/#Ariandne.groovy#
new file mode 100644
index 0000000..6c0cf17
--- /dev/null
+++ b/developer/deprecated/#Ariandne.groovy#
@@ -0,0 +1,635 @@
+class AriadneGraph {
+
+ // Instance variables for graph data if needed
+ Map node_map = [:]
+ List node_f_list = []
+
+ // Constructor to accept a graph definition (node_map and node_f_list)
+ AriadneGraph( Map node_map ,List node_f_list ){
+ this.node_map = node_map ?: [:]
+ this.node_f_list = node_f_list ?: []
+ }
+
+ /*--------------------------------------------------------------------------------
+ File utility functions
+ */
+
+ static Map unpack_file_path( String file_fp ){
+ def file = new File( file_fp )
+
+ def parent_dp = file.getParent()
+ def file_fn = file.getName()
+ def file_fn_base = file_fn.lastIndexOf('.') > 0 ? file_fn[ 0..file_fn.lastIndexOf('.') - 1 ] : file_fn
+ def file_fn_ext = file_fn.lastIndexOf('.') > 0 ? file_fn[ file_fn.lastIndexOf('.') + 1..-1 ] : ''
+
+ return [
+ parent_dp: parent_dp
+ ,file_fn: file_fn
+ ,file_fn_base: file_fn_base
+ ,file_fn_ext: file_fn_ext
+ ]
+ }
+
+ static boolean file_exists_q( String node_label ){
+ def node_path = Paths.get( node_label )
+ return Files.exists( node_path )
+ }
+
+ /*--------------------------------------------------------------------------------
+ Node type checks and marking
+ */
+
+ static Set all_node_type_set = ['symbol' ,'path' ,'leaf' ,'generator'] as Set
+ static Set persistent_node_mark_set = ['cycle_member' ,'wellformed' ,'build_failed'] as Set
+
+ static boolean leaf_q( Map node ){
+ return node && node.type == 'leaf'
+ }
+
+ static boolean has_mark( Map node ){
+ return node?.mark?.isNotEmpty()
+ }
+
+ static void set_mark( Map node ,String mark ){
+ node.mark = node.mark ?: [] as Set
+ node.mark << mark
+ }
+
+ static void clear_mark( Map node ,String mark ){
+ node?.mark?.remove( mark )
+ }
+
+ static boolean marked_good_q( Map node ){
+ return node && node.mark && ( 'wellformed' in node.mark ) && !( 'cycle_member' in node.mark ) && !( 'build_failed' in node.mark )
+ }
+
+ /*--------------------------------------------------------------------------------
+ Well-formed Node Check
+ */
+
+ static Set all_form_error_set = [
+ 'no_node'
+ ,'node_must_have_label'
+ ,'label_must_be_string'
+ ,'node_must_have_type'
+ ,'bad_node_type'
+ ,'neighbor_value_must_be_list'
+ ,'neighbor_reference_must_be_string'
+ ,'mark_property_value_must_be_set'
+ ,'unregistered_mark'
+ ,'missing_required_build_code'
+ ,'leaf_given_neighbor_property'
+ ,'leaf_given_build_property'
+ ] as Set
+
+ static Set wellformed_q( Map node ){
+ def form_error_set = [] as Set
+
+ if( !node ){
+ form_error_set << 'no_node'
+ return form_error_set
+ }
+
+ if( !node.label )
+ form_error_set << 'node_must_have_label'
+ else if( !( node.label instanceof String ) )
+ form_error_set << 'label_must_be_string'
+
+ if( !node.type )
+ form_error_set << 'node_must_have_type'
+ else if( !( node.type instanceof String ) || !( node.type in all_node_type_set ) )
+ form_error_set << 'bad_node_type'
+
+ if( node.neighbor ){
+ if( !( node.neighbor instanceof List ) )
+ form_error_set << 'neighbor_value_must_be_list'
+ else if( !( node.neighbor.every { it instanceof String } ) )
+ form_error_set << 'neighbor_reference_must_be_string'
+ }
+
+ if( node.mark ){
+ if( !( node.mark instanceof Set ) )
+ form_error_set << 'mark_property_value_must_be_set'
+ else if( !( node.mark.every { it in persistent_node_mark_set } ) )
+ form_error_set << 'unregistered_mark'
+ }
+
+ if( node.type == 'path' && ( !node.build || !( node.build instanceof Closure ) ) )
+ form_error_set << 'missing_required_build_code'
+
+ if( node.type == 'leaf' ){
+ if( node.neighbor ) form_error_set << 'leaf_given_neighbor_property'
+ if( node.build ) form_error_set << 'leaf_given_build_property'
+ }
+
+ return form_error_set
+ }
+
+ /*--------------------------------------------------------------------------------
+ Graph traversal and build functions
+ */
+
+ def lookup( String node_label ,boolean verbose = false ){
+ def lookup_node = node_map[ node_label ]
+ if( !lookup_node ){
+ 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 && verbose ) println "lookup:: Node ${node_label} could not be found."
+ return lookup_node
+ }
+
+ def run_build_scripts_f( List root_node_labels ,boolean verbose = true ){
+ if( root_node_labels.isEmpty() ) return
+
+ def node_function = { node ,error_token_set ->
+
+ if( !can_be_built_q( node ) ){
+ println( "Skipping build for ${node.label} due to dependency problems" )
+ return
+ }
+ if( !should_be_built_q( node ) ){
+ if( verbose ) println( "${node.label} already up to date" )
+ return
+ }
+
+ println( "Running build script for ${node.label}" )
+ node.build( node ,node.neighbor )
+
+ if( should_be_built_q( node ) ){
+ println( "Build failed for ${node.label}" )
+ set_mark( node ,'build_failed' )
+ }
+ }
+
+ println( "run_build_scripts_f:: running ..." )
+ all_DAG_DF( root_node_labels ,node_function ,verbose )
+ }
+
+ // Add the rest of your methods here as instance/static methods based on whether they depend on the graph instance
+
+}
+/*
+ Some terms:
+
+ a node is either malformed or 'wellformed'. A wellformed node meets
+ the criteria set forth by `well_formed_node_q`.
+
+ 'node': a dictionary, hopefully a wellformed one
+ 'file node': a 'path' or 'leaf' type node.
+ 'node file': for 'path' or 'leaf' nodes, path relative to the developer's directory.
+
+ This code makes use of the following node properties:
+ - label: Unique label string. Used to reference nodes.
+ - type: The type of the node: 'leaf' ,'symbol' ,or 'path'.
+ - neighbor: list of the neighbor nodes labels
+ - must_have: list of node labels
+ - build: Code that builds the node.
+ - mark: a set of 'mark' tokens optionally placed on a node
+
+ For 'path' and 'leaf' type nodes, the node label is a file path. Relative paths
+ are relative to the developer's directory.
+
+ The dependency graph is defined by the `lookup` function. `lookup` when given
+ a node label returns the node, if found, or null.
+
+*/
+
+/*--------------------------------------------------------------------------------
+ file utility functions
+*/
+
+def unpack_file_path = { file_fp ->
+ def file = new File(file_fp)
+
+ // Get the parent directory path
+ def parent_dp = file_fn.getParent()
+
+ // Get the file name (with extension)
+ def file_fn = file_fn.getName()
+
+ // Split the file name into base and extension
+ def file_fn_base = file_fn.lastIndexOf('.') > 0 ? file_fn[0..file_fn.lastIndexOf('.') - 1] : file_fn
+ def file_fn_ext = file_fn.lastIndexOf('.') > 0 ? file_fn[file_fn.lastIndexOf('.') + 1..-1] : ''
+
+ // Return the components as a map for easier use
+ return [
+ parent_dp: parent_dp
+ ,file_fn: file_fn
+ ,file_fn_base: file_fn_base
+ ,file_fn_ext: file_fn_ext
+ ]
+}
+
+def file_exists_q(node_label) {
+ def node_path = Paths.get(node_label)
+ return Files.exists(node_path)
+}
+
+
+/*-------------------------------------------------------------------------------
+ DAG adjectives
+
+ node undefined for undefined node?
+*/
+
+// A node is type 'leaf' when the node file can not be deleted or replaced.
+// This type used to be called 'primary', which might be a better term.
+
+def all_node_type_set = ['symbol' ,'path' ,'leaf' ,'generator'] as Set
+
+def persistent_node_mark_set = ['cycle_member' ,'wellformed' ,'build_failed'] as Set
+def leaf_q = { node -> node && node.type && node.type == 'leaf' }
+
+// mark
+def has_mark = { node -> node.mark && !node.mark.isEmpty() }
+def set_mark(node ,mark){
+ node.mark = node.mark ?: [] as Set
+ node.mark << mark
+}
+def clear_mark(node ,mark){
+ if( node.mark ) node.mark.remove(mark)
+}
+def marked_good_q(node){
+ return (
+ node
+ && node.mark
+ && ('wellformed' in node.mark)
+ && !('cycle_member' in node.mark)
+ && !('build_failed' in node.mark)
+ )
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ Wellformed Node Check
+
+ The developer defines the nodes, so their form needs to be checked.
+
+ early bail on node not defined
+ legal path in the build directory makedir -p ... not really a acquirement
+
+*/
+def all_form_error_set = [
+ 'no_node'
+ ,'node_must_have_label'
+ ,'label_must_be_string'
+ ,'node_must_have_type'
+ ,'bad_node_type'
+ ,'neighbor_value_must_be_list'
+ ,'neighbor_reference_must_be_string'
+ ,'mark_property_value_must_be_set'
+ ,'unregistered_mark'
+ ,'missing_required_build_code'
+ ,'leaf_given_neighbor_property'
+ ,'leaf_given_build_property'
+]
+def wellformed_q = { node ->
+ def form_error_set = [] as Set
+
+ if( !node ){
+ form_error_set << 'no_node'
+ return form_error_set
+ }
+
+ if( !node.label )
+ form_error_set << 'node_must_have_label'
+ else if( !(node.label instanceof String) )
+ form_error_set << 'label_must_be_string'
+
+ if( !node.type )
+ form_error_set << 'node_must_have_type'
+ else if( !(node.type instanceof String) || !(node.type in all_node_type_set) )
+ form_error_set << 'bad_node_type'
+
+ if( node.neighbor ){
+ if( !(node.neighbor instanceof List) )
+ form_error_set << 'neighbor_value_must_be_list'
+ else if( !(node.neighbor.every { it instanceof String }) )
+ form_error_set << 'neighbor_reference_must_be_string'
+ }
+
+ if( node.mark ){
+ if( !(node.mark instanceof Set) )
+ form_error_set << 'mark_property_value_must_be_set'
+ else if( !(node.mark.every { it in persistent_node_mark_set }) )
+ form_error_set << 'unregistered_mark'
+ }
+
+ // Removed test about symbol build needing neighbors
+
+ if(
+ node.type == "path"
+ && (!node.build || !(node.build instanceof Closure))
+ )
+ form_error_set << 'missing_required_build_code'
+
+ // Removed test about path needing neighbors
+
+ // Can a primary file have neighbors or a build script? Our build model
+ // has the node file as a target, the build script as a means of building
+ // the target (hence its name), and the dependencies as inputs to the build
+ // script. So in the spirit of this model, we will say "no".
+ if( node.type == "leaf" ){
+ if( node.neighbor ) form_error_set << 'leaf_given_neighbor_property'
+ if( node.build ) form_error_set << 'leaf_given_build_property'
+ }
+
+ return form_error_set
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ A well formed graph checker. Traverses entire graph and marks nodes
+ that are not well formed or that are part of a cycle.
+
+*/
+
+// given a node label list, adds the 'wellformed' mark to wellformed nodes.
+def mark_the_wellformed_f(node_label_list ,boolean verbose = true){
+ def all_wellformed = true
+
+ def neighbors = node_label_list.collect{ neighbor_label ->
+ def neighbor_node = lookup(neighbor_label)
+ def form_errors = wellformed_q(neighbor_node)
+ if(form_errors.isEmpty()){
+ neighbor_node.mark = neighbor_node.mark ?: [] as Set
+ neighbor_node.mark << 'wellformed'
+ } else {
+ all_wellformed = false
+ if(verbose){
+ if(neighbor_node.label && neighbor_node.label.length() > 0){
+ print("node ${neighbor_node.label} is malformed due to:")
+ } else {
+ print("anonymous node is malformed due to:")
+ }
+ form_errors.each { error -> print(" ${error}") }
+ println("")
+ }
+ }
+ neighbor_label
+ }
+
+ return all_wellformed ? 'all_wellformed' : 'exists_malformed'
+}
+
+// descends un-visited leftmost path, while marking nodes as wellformed and if
+// they are part of a cycle.
+def markup_graph_f_descend(path_stack ,boolean verbose = true){
+ def ret_value = [] as Set
+ def local_path = path_stack.collect{ it[0] }
+ def local_node_label = local_path[-1]
+ def cycle_start_index
+
+ do{
+ // Check for a cycle in the local path
+ cycle_start_index = local_path[0..-2].findIndexOf{ it == local_node_label }
+ if(cycle_start_index != -1){ // Cycle detected
+ ret_value << 'cycle_found'
+ if(verbose) print "markup_graph_f_descend:: dependency cycle found:"
+ local_path[cycle_start_index..-1].each{ cycle_node_label ->
+ def cycle_node = lookup(cycle_node_label)
+ 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 ""
+ // we can not continue searching after the loop so ,we pop back to treat
+ // the first node in the loop as though a leaf node.
+ path_stack = path_stack[0..cycle_start_index]
+ return ret_value
+ }
+
+ // a 'de-facto' leaf node test subtleties here because we have not yet
+ // determined if the nodes we are wellformed. This is purposeful ,as
+ // this function does not know about the relationships between the
+ // possible error marks.
+ def local_node = lookup(local_node_label)
+ if(local_node.neighbor.isEmpty()){
+ ret_value << 'defacto_leaf_node'
+ return ret_value
+ }
+
+ // Mark the wellformed nodes and get the result
+ def result = mark_the_wellformed_f(local_node.neighbor ,verbose)
+ if(result == 'exists_malformed'){
+ ret_value << 'exists_malformed'
+ }
+
+ // Descend further into the tree.
+ path_stack << local_node.neighbor.clone()
+ local_node_label = local_node.neighbor[0]
+ local_path << local_node_label
+ }while(true)
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ This function defines the graph.
+
+ Lookup attempts to lookup a node label in the node_map, and failing that, it
+ tries each label pattern recognition function in order.
+
+ lookup_marked_good can be run after `wellformed_graph_f` has marked up the
+ graph. It will only return a node if a) found b) that is 'good'.
+ Note the `marked_good_q` adjective above.
+
+*/
+def lookup(node_label ,verbose = false){
+ 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:: Node ${node_label} could not be found."
+ return null
+ }
+ return lookup_node
+}
+
+// mark aware lookup function
+def lookup_marked_good(node_label ,verbose = false){
+ def node = lookup(node_label ,verbose)
+ if( node && marked_good_q(node) ) return node;
+ return null;
+}
+
+
+/*
+ Given `root_node_labels` of a DAG. Applies `node_function` to each node in a
+ depth-first traversal order. Returns a set of error tokens encountered
+ during traversal.
+
+ `wellformed_graph_q` must be run on the DAG before this function is called ,or
+ `lookup_marked_good` will not function correctly.
+*/
+def all_DAG_DF(root_node_labels ,node_function ,boolean verbose = true) {
+ def error_token_set = [] as Set
+
+ if (root_node_labels.isEmpty()) return error_token_set
+
+ def visited = [] as Set
+ def in_traversal_order = []
+ def stack = []
+
+ root_node_labels.each { root_label ->
+ stack << root_label
+ }
+
+ do {
+ def node_label = stack.pop()
+
+ def node = lookup_marked_good(node_label ,verbose)
+ if (!node) {
+ error_token_set << 'lookup_fail'
+ continue
+ }
+
+ if (node.label in visited) continue
+ visited << node.label
+
+ in_traversal_order << node
+
+ node.neighbor.each { neighbor_label ->
+ stack << neighbor_label
+ }
+ } while (!stack.isEmpty())
+
+ in_traversal_order.reverse().each { node ->
+ node_function(node ,error_token_set ,verbose)
+ }
+
+ return error_token_set
+}
+
+
+/*--------------------------------------------------------------------------------
+ run the build scripts
+ depends upon is_acyclic having already marked up the graph.
+*/
+
+import java.nio.file.Files
+import java.nio.file.Paths
+
+// a symbol dependency is good ,as long as it is built before the node in question
+def good_dependency_q(node_labels) {
+ return node_labels.every { node_label ->
+ def node = lookup_marked_good(node_label)
+ if (!node) return false
+ if (node.type in ['path' ,'leaf'] && !file_exists_q(node.label)) return false
+ return true
+ }
+}
+
+/*
+ Given a node label and a list of node labels ,returns true if the file at the
+ node label in the first argument is newer than all the files at the
+ corresponding node labels in the second list.
+*/
+def newer_than_all(node_label ,node_label_list) {
+ def node_path = Paths.get(node_label)
+ if (!Files.exists(node_path)) return false
+
+ def node_last_modified = Files.getLastModifiedTime(node_path).toMillis()
+
+ return node_label_list.every { label ->
+ def path = Paths.get(label)
+ if (!Files.exists(path)) return false
+ def last_modified = Files.getLastModifiedTime(path).toMillis()
+ return node_last_modified > last_modified
+ }
+}
+
+def can_be_built_q(node){
+ if( !marked_good_q(node) ) return false;
+ if(
+ (node.type == 'symbol' || type == 'path')
+ && !good_dependency_q( node.neighbor )
+ ){
+ return false
+ }
+ if(
+ node.type == 'leaf'
+ && !file_exists_q(node.label)
+ ){
+ return false;
+ }
+ return true
+}
+
+// `can_be_build_q` must be true for this to be meaningful:
+def should_be_built_q(node ,verbose = true) {
+ if(node.type == 'leaf') return false
+ if(node.type == 'symbol') return true
+ if( node.type == 'path') return !newer_than_all(node.label ,node.neighbor)
+ println("should_be_build_q:: unrecognized node type ,so assuming it should not be built.")
+ return false
+}
+
+/*
+ Runs build scripts for the given root_node_labels in a depth-first traversal order.
+ Uses `all_DAG_DF` to traverse the graph and applies the build function to each node.
+
+ Be sure that `wellformed_graph_q` has been run on DAG.
+
+ Be sure that the 'build_failed' marks have been cleared if this is not the
+ first build attempt.
+
+*/
+def run_build_scripts_f(root_node_labels ,boolean verbose = true){
+ if( root_node_labels.isEmpty() ) return
+
+ // Define the function to be applied to each node
+ def node_function = { node ,error_token_set ->
+
+ if( !can_be_built_q(node) ){
+ println("Skipping build for ${node.label} due to dependency problems or found leaf is missing")
+ return
+ }
+
+ if( !should_be_built_q(node) ){
+ if(verbose) println("${node.label} already up to date")
+ return
+ }
+
+ // if we get here, node can and should be built
+
+ println("Running build script for ${node.label}")
+ node.build(node ,node.neighbor)
+
+ // Check if the build updated the target file
+ if( should_be_built_q(node) ){
+ println("Build failed for ${node.label}")
+ set_mark(node ,'build_failed')
+ }
+
+ }
+
+ println("run_build_scripts_f:: running ...")
+ // Apply the function to all nodes in a depth-first manner
+ all_DAG_DF(root_node_labels ,node_function ,verbose)
+}
+
+
+// LocalWords: FN FPL DN DNL RuleNameListRegx RuleNameList PrintVisitorMethod
+// LocalWords: PrintVisitor SyntaxAnnotate wellformed defacto acyclic
+// LocalWords: wellformed unvisited
diff --git a/developer/deprecated/.githolder b/developer/deprecated/.githolder
new file mode 100644
index 0000000..e69de29
diff --git a/developer/deprecated/.gitignore b/developer/deprecated/.gitignore
deleted file mode 100644
index 120f485..0000000
--- a/developer/deprecated/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!/.gitignore
diff --git a/developer/deprecated/Ariandne.groovy b/developer/deprecated/Ariandne.groovy
new file mode 100644
index 0000000..a657f0d
--- /dev/null
+++ b/developer/deprecated/Ariandne.groovy
@@ -0,0 +1,457 @@
+/*
+ Some terms:
+
+ a node is either malformed or 'wellformed'. A wellformed node meets
+ the criteria set forth by `well_formed_node_q`.
+
+ 'node': a dictionary, hopefully a wellformed one
+ 'file node': a 'path' or 'leaf' type node.
+ 'node file': for 'path' or 'leaf' nodes, path relative to the developer's directory.
+
+ This code makes use of the following node properties:
+ - label: Unique label string. Used to reference nodes.
+ - type: The type of the node: 'leaf' ,'symbol' ,or 'path'.
+ - neighbor: list of the neighbor nodes labels
+ - must_have: list of node labels
+ - build: Code that builds the node.
+ - mark: a set of 'mark' tokens optionally placed on a node
+
+ For 'path' and 'leaf' type nodes, the node label is a file path. Relative paths
+ are relative to the developer's directory.
+
+ The dependency graph is defined by the `lookup` function. `lookup` when given
+ a node label returns the node, if found, or null.
+
+*/
+
+/*--------------------------------------------------------------------------------
+ file utility functions
+*/
+
+def unpack_file_path = { file_fp ->
+ def file = new File(file_fp) // Renamed to 'file' to avoid overwriting 'file_fn'
+
+ // Get the parent directory path
+ def parent_dp = file.getParent()
+
+ // Get the file name (with extension)
+ def file_fn = file.getName()
+
+ // Split the file name into base and extension
+ def file_fn_base = file_fn.lastIndexOf('.') > 0 ? file_fn[0..file_fn.lastIndexOf('.') - 1] : file_fn
+ def file_fn_ext = file_fn.lastIndexOf('.') > 0 ? file_fn[file_fn.lastIndexOf('.') + 1..-1] : ''
+
+ // Return the components as a map for easier use
+ return [
+ parent_dp: parent_dp
+ ,file_fn: file_fn
+ ,file_fn_base: file_fn_base
+ ,file_fn_ext: file_fn_ext
+ ]
+}
+
+def file_exists_q(node_label) {
+ def node_path = Paths.get(node_label)
+ return Files.exists(node_path)
+}
+
+
+/*-------------------------------------------------------------------------------
+ DAG adjectives
+
+ node undefined for undefined node?
+*/
+
+// A node is type 'leaf' when the node file can not be deleted or replaced.
+// This type used to be called 'primary', which might be a better term.
+
+def all_node_type_set = ['symbol' ,'path' ,'leaf' ,'generator'] as Set
+
+def persistent_node_mark_set = ['cycle_member' ,'wellformed' ,'build_failed'] as Set
+def leaf_q = { node -> node && node.type && node.type == 'leaf' }
+
+// mark
+def has_mark = { node -> node.mark && !node.mark.isEmpty() }
+def set_mark(node ,mark){
+ node.mark = node.mark ?: [] as Set
+ node.mark << mark
+}
+def clear_mark(node ,mark){
+ if( node.mark ) node.mark.remove(mark)
+}
+def marked_good_q(node){
+ return (
+ node
+ && node.mark
+ && ('wellformed' in node.mark)
+ && !('cycle_member' in node.mark)
+ && !('build_failed' in node.mark)
+ )
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ Wellformed Node Check
+
+ The developer defines the nodes, so their form needs to be checked.
+
+ early bail on node not defined
+ legal path in the build directory makedir -p ... not really a acquirement
+
+*/
+def all_form_error_set = [
+ 'no_node'
+ ,'node_must_have_label'
+ ,'label_must_be_string'
+ ,'node_must_have_type'
+ ,'bad_node_type'
+ ,'neighbor_value_must_be_list'
+ ,'neighbor_reference_must_be_string'
+ ,'mark_property_value_must_be_set'
+ ,'unregistered_mark'
+ ,'missing_required_build_code'
+ ,'leaf_given_neighbor_property'
+ ,'leaf_given_build_property'
+]
+def wellformed_q = { node ->
+ def form_error_set = [] as Set
+
+ if( !node ){
+ form_error_set << 'no_node'
+ return form_error_set
+ }
+
+ if( !node.label )
+ form_error_set << 'node_must_have_label'
+ else if( !(node.label instanceof String) )
+ form_error_set << 'label_must_be_string'
+
+ if( !node.type )
+ form_error_set << 'node_must_have_type'
+ else if( !(node.type instanceof String) || !(node.type in all_node_type_set) )
+ form_error_set << 'bad_node_type'
+
+ if( node.neighbor ){
+ if( !(node.neighbor instanceof List) )
+ form_error_set << 'neighbor_value_must_be_list'
+ else if( !(node.neighbor.every { it instanceof String }) )
+ form_error_set << 'neighbor_reference_must_be_string'
+ }
+
+ if( node.mark ){
+ if( !(node.mark instanceof Set) )
+ form_error_set << 'mark_property_value_must_be_set'
+ else if( !(node.mark.every { it in persistent_node_mark_set }) )
+ form_error_set << 'unregistered_mark'
+ }
+
+ // Removed test about symbol build needing neighbors
+
+ if(
+ node.type == "path"
+ && (!node.build || !(node.build instanceof Closure))
+ )
+ form_error_set << 'missing_required_build_code'
+
+ // Removed test about path needing neighbors
+
+ // Can a primary file have neighbors or a build script? Our build model
+ // has the node file as a target, the build script as a means of building
+ // the target (hence its name), and the dependencies as inputs to the build
+ // script. So in the spirit of this model, we will say "no".
+ if( node.type == "leaf" ){
+ if( node.neighbor ) form_error_set << 'leaf_given_neighbor_property'
+ if( node.build ) form_error_set << 'leaf_given_build_property'
+ }
+
+ return form_error_set
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ A well formed graph checker. Traverses entire graph and marks nodes
+ that are not well formed or that are part of a cycle.
+
+*/
+
+// given a node label list, adds the 'wellformed' mark to wellformed nodes.
+def mark_the_wellformed_f(node_label_list ,boolean verbose = true){
+ def all_wellformed = true
+
+ def neighbors = node_label_list.collect{ neighbor_label ->
+ def neighbor_node = lookup(neighbor_label)
+ def form_errors = wellformed_q(neighbor_node)
+ if(form_errors.isEmpty()){
+ neighbor_node.mark = neighbor_node.mark ?: [] as Set
+ neighbor_node.mark << 'wellformed'
+ } else {
+ all_wellformed = false
+ if(verbose){
+ if(neighbor_node.label && neighbor_node.label.length() > 0){
+ print("node ${neighbor_node.label} is malformed due to:")
+ } else {
+ print("anonymous node is malformed due to:")
+ }
+ form_errors.each { error -> print(" ${error}") }
+ println("")
+ }
+ }
+ neighbor_label
+ }
+
+ return all_wellformed ? 'all_wellformed' : 'exists_malformed'
+}
+
+// descends un-visited leftmost path, while marking nodes as wellformed and if
+// they are part of a cycle.
+def markup_graph_f_descend(path_stack ,boolean verbose = true){
+ def ret_value = [] as Set
+ def local_path = path_stack.collect{ it[0] }
+ def local_node_label = local_path[-1]
+ def cycle_start_index
+
+ do{
+ // Check for a cycle in the local path
+ cycle_start_index = local_path[0..-2].findIndexOf{ it == local_node_label }
+ if(cycle_start_index != -1){ // Cycle detected
+ ret_value << 'cycle_found'
+ if(verbose) print "markup_graph_f_descend:: dependency cycle found:"
+ local_path[cycle_start_index..-1].each{ cycle_node_label ->
+ def cycle_node = lookup(cycle_node_label)
+ 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 ""
+ // we can not continue searching after the loop so ,we pop back to treat
+ // the first node in the loop as though a leaf node.
+ path_stack = path_stack[0..cycle_start_index]
+ return ret_value
+ }
+
+ // a 'de-facto' leaf node test subtleties here because we have not yet
+ // determined if the nodes we are wellformed. This is purposeful ,as
+ // this function does not know about the relationships between the
+ // possible error marks.
+ def local_node = lookup(local_node_label)
+ if(local_node.neighbor.isEmpty()){
+ ret_value << 'defacto_leaf_node'
+ return ret_value
+ }
+
+ // Mark the wellformed nodes and get the result
+ def result = mark_the_wellformed_f(local_node.neighbor ,verbose)
+ if(result == 'exists_malformed'){
+ ret_value << 'exists_malformed'
+ }
+
+ // Descend further into the tree.
+ path_stack << local_node.neighbor.clone()
+ local_node_label = local_node.neighbor[0]
+ local_path << local_node_label
+ }while(true)
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ This function defines the graph.
+
+ Lookup attempts to lookup a node label in the node_map, and failing that, it
+ tries each label pattern recognition function in order.
+
+ lookup_marked_good can be run after `wellformed_graph_f` has marked up the
+ graph. It will only return a node if a) found b) that is 'good'.
+ Note the `marked_good_q` adjective above.
+
+*/
+def lookup(node_label ,verbose = false){
+ 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:: Node ${node_label} could not be found."
+ return null
+ }
+ return lookup_node
+}
+
+// mark aware lookup function
+def lookup_marked_good(node_label ,verbose = false){
+ def node = lookup(node_label ,verbose)
+ if( node && marked_good_q(node) ) return node;
+ return null;
+}
+
+
+/*
+ Given `root_node_labels` of a DAG. Applies `node_function` to each node in a
+ depth-first traversal order. Returns a set of error tokens encountered
+ during traversal.
+
+ `wellformed_graph_q` must be run on the DAG before this function is called ,or
+ `lookup_marked_good` will not function correctly.
+*/
+def all_DAG_DF(root_node_labels ,node_function ,boolean verbose = true) {
+ def error_token_set = [] as Set
+
+ if (root_node_labels.isEmpty()) return error_token_set
+
+ def visited = [] as Set
+ def in_traversal_order = []
+ def stack = []
+
+ root_node_labels.each { root_label ->
+ stack << root_label
+ }
+
+ do {
+ def node_label = stack.pop()
+
+ def node = lookup_marked_good(node_label ,verbose)
+ if (!node) {
+ error_token_set << 'lookup_fail'
+ continue
+ }
+
+ if (node.label in visited) continue
+ visited << node.label
+
+ in_traversal_order << node
+
+ node.neighbor.each { neighbor_label ->
+ stack << neighbor_label
+ }
+ } while (!stack.isEmpty())
+
+ in_traversal_order.reverse().each { node ->
+ node_function(node ,error_token_set ,verbose)
+ }
+
+ return error_token_set
+}
+
+
+/*--------------------------------------------------------------------------------
+ run the build scripts
+ depends upon is_acyclic having already marked up the graph.
+*/
+
+import java.nio.file.Files
+import java.nio.file.Paths
+
+// a symbol dependency is good ,as long as it is built before the node in question
+def good_dependency_q(node_labels) {
+ return node_labels.every { node_label ->
+ def node = lookup_marked_good(node_label)
+ if (!node) return false
+ if (node.type in ['path' ,'leaf'] && !file_exists_q(node.label)) return false
+ return true
+ }
+}
+
+/*
+ Given a node label and a list of node labels ,returns true if the file at the
+ node label in the first argument is newer than all the files at the
+ corresponding node labels in the second list.
+*/
+def newer_than_all(node_label ,node_label_list) {
+ def node_path = Paths.get(node_label)
+ if (!Files.exists(node_path)) return false
+
+ def node_last_modified = Files.getLastModifiedTime(node_path).toMillis()
+
+ return node_label_list.every { label ->
+ def path = Paths.get(label)
+ if (!Files.exists(path)) return false
+ def last_modified = Files.getLastModifiedTime(path).toMillis()
+ return node_last_modified > last_modified
+ }
+}
+
+def can_be_built_q(node){
+ if( !marked_good_q(node) ) return false;
+ if(
+ (node.type == 'symbol' || type == 'path')
+ && !good_dependency_q( node.neighbor )
+ ){
+ return false
+ }
+ if(
+ node.type == 'leaf'
+ && !file_exists_q(node.label)
+ ){
+ return false;
+ }
+ return true
+}
+
+// `can_be_build_q` must be true for this to be meaningful:
+def should_be_built_q(node ,verbose = true) {
+ if(node.type == 'leaf') return false
+ if(node.type == 'symbol') return true
+ if( node.type == 'path') return !newer_than_all(node.label ,node.neighbor)
+ println("should_be_build_q:: unrecognized node type ,so assuming it should not be built.")
+ return false
+}
+
+/*
+ Runs build scripts for the given root_node_labels in a depth-first traversal order.
+ Uses `all_DAG_DF` to traverse the graph and applies the build function to each node.
+
+ Be sure that `wellformed_graph_q` has been run on DAG.
+
+ Be sure that the 'build_failed' marks have been cleared if this is not the
+ first build attempt.
+
+*/
+def run_build_scripts_f(root_node_labels ,boolean verbose = true){
+ if( root_node_labels.isEmpty() ) return
+
+ // Define the function to be applied to each node
+ def node_function = { node ,error_token_set ->
+
+ if( !can_be_built_q(node) ){
+ println("Skipping build for ${node.label} due to dependency problems or found leaf is missing")
+ return
+ }
+
+ if( !should_be_built_q(node) ){
+ if(verbose) println("${node.label} already up to date")
+ return
+ }
+
+ // if we get here, node can and should be built
+
+ println("Running build script for ${node.label}")
+ node.build(node ,node.neighbor)
+
+ // Check if the build updated the target file
+ if( should_be_built_q(node) ){
+ println("Build failed for ${node.label}")
+ set_mark(node ,'build_failed')
+ }
+
+ }
+
+ println("run_build_scripts_f:: running ...")
+ // Apply the function to all nodes in a depth-first manner
+ all_DAG_DF(root_node_labels ,node_function ,verbose)
+}
+
+
+// LocalWords: FN FPL DN DNL RuleNameListRegx RuleNameList PrintVisitorMethod
+// LocalWords: PrintVisitor SyntaxAnnotate wellformed defacto acyclic
+// LocalWords: wellformed unvisited
diff --git a/developer/deprecated/dependency_graph_definition.groovy b/developer/deprecated/dependency_graph_definition.groovy
new file mode 100644
index 0000000..ec9028b
--- /dev/null
+++ b/developer/deprecated/dependency_graph_definition.groovy
@@ -0,0 +1,387 @@
+/*
+ Here the developer describes the build environment and the graph of
+ dependencies between files to be built.
+
+ Each file is given a 'node'. The node label is a path
+ to the file, existing or not. Node's can also be 'symbolic'
+ in that the label does not correspond to a file.
+
+ File paths are relative to the developer's directory. Also
+ known as $DEVELOPER_HOME.
+
+ The program that processes this file is
+ `executor/build_from_dependency_graph.groovy`. There are more notes and
+ definitions there.
+*/
+
+
+/*--------------------------------------------------------------------------------
+ The build environment
+
+ Each tool has its '_IN' and '_OUT' directories that are referenced in
+ their build scripts. This keeps references in the build script local.
+ (Flow of files between tools can be seen in the overlapping definitions
+ among the various _IN and _OUT directories.)
+
+ These suffixes are sometimes used to eliminate ambiguity:
+
+ _FN = File Name
+ _FP = File Path (last element of the FP is the FN)
+ _DN = Directory Name
+ _DP = Directory path (last element of the DP is the DN)
+
+ _FNL = File Name list
+ _FPL = File Path List
+ _DNL = Directory path list
+ _DPL = Directory path list
+
+ Files in a _LEAF directory can not be built, and should never be deleted by the tools.
+
+*/
+
+def env = [:]
+
+// Required shell environment variables
+def varName_List = [
+ 'REPO_HOME'
+ ,'PROJECT'
+ ,'ENV_BUILD_VERSION'
+ ,'DEVELOPER_HOME'
+]
+varName_List.each { varName ->
+ env[varName] = System.getenv(varName) ?: ""
+}
+
+// Optional shell environment variables
+def varNameOptional_List = [
+ 'CLASSPATH'
+]
+varNameOptional_List.each { varName ->
+ env[varName] = System.getenv(varName) ?: ""
+}
+
+env.CLASSPATH += ":${env.ANTLR_JAR}"
+
+// tools used in build scripts
+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
+
+def dir_map = [
+ 'ANTLR_IN_LEAF' : 'ANTLR/'
+ ,'ANTLR_OUT' : 'javac/ANTLR/'
+ ,'ANTLR_OUT_PARENT' : 'javac/' // required by the antlr command to be a base for OUT
+ ,'EXECUTOR_IN' : 'executor/'
+ ,'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/'
+]
+
+env.CLASSPATH += ":${dir_map.JVM_IN}"
+
+dir_map.JAVA_COMP_IN_LIST =
+ "${dir_map.JAVA_COMP_IN_LEAF}"
+ +":${dir_map.JAVA_COMP_IN_ANTLR}"
+ +":${dir_map.JAVA_COMP_IN_SYN}"
+
+//--------------------------------------------------------------------------------
+// required environment variable check. Will add additional preface checks here
+// should they become available.
+//
+
+// a helper function for `environment_check`
+def print_missing_var_list(missing_var_list){
+ if(missing_var_list.isEmpty()){
+ // Print nothing if the list is empty
+ return
+ } else if(missing_var_list.size() == 1){
+ println "This environment variable was not set: ${missing_var_list[0]}"
+ } else {
+ println "These environment variables were not set: ${missing_var_list.join(' ,')}"
+ }
+}
+
+task environment_check {
+ dependsOn ':installTools'
+ doFirst {
+
+ println "CLASSPATH: ${env.CLASSPATH}"
+ println "JAVA_COMP_IN_LIST: ${dir_map.JAVA_COMP_IN_LIST}"
+
+ // did the required variables have definitions?
+ def error_missing = false
+ def error_project = false
+ def missing_var_list = [] // To collect missing environment variables
+ varName_List.each { varName ->
+ if(!env[varName]){
+ error_missing = true
+ missing_var_list << varName
+ }
+ }
+ print_missing_var_list(missing_var_list)
+
+ // did the required variables have definitions?
+ 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 .."
+ }
+}
+
+/*--------------------------------------------------------------------------------
+ Map keyed on label node definitions
+
+*/
+
+def node_map = [
+
+ "all" : [
+ ,type: "symbol"
+ ,neighbor: [
+ "ANTLR_OUT_FL"
+ ,"RuleNameList"
+ ,"RuleNameListRegx"
+ ,"Synthesize_SyntaxAnnotate"
+ ,"Synthesize_SyntaxAnnotate_PrintVisitor"
+ ,"Synthesize_SyntaxAnnotate_PrintVisitorMethod"
+ ]
+ ]
+
+ "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.class" : [
+ type: 'path' , // It's a path type node
+ ,neighbor: [
+ "${dir_map.JAVA_COMP_IN_LEAF}/Synthesize_SyntaxAnnotate.java" , // Dependency
+ "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" // Dependency
+ ]
+ ,build: { node ,neighbor ->
+ def javac_cmd = "${JAVA_COMP_FP} -d ${dir_map.JAVA_COMP_OUT} ${neighbor.join(' ')}"
+ javac_cmd.execute().waitFor()
+ }
+ ]
+
+ ,"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"
+ ]
+ ]
+]
+
+
+
+//--------------------------------------------------------------------------------
+// node making functions
+//
+
+// javac/leaf/ returns leaf node for 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: []
+ ]
+}
+
+// given executor/ returns node to build script wrapping jvm/.jar
+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]
+ ,must_have: [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
+ }
+ ]
+}
+
+// given javac/ANTLR/.java, returns node to build grammar .g4
+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]
+ ]
+}
+
+// given .class returns node to build .class from .java
+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_PRIMARY_DIR']}/${baseName}.java"
+ def javaFile = new File(javaFilePath)
+
+ if( !javaFile.exists() ){
+ return [status: "no_match"]
+ }
+
+ return [
+ status: "matched",
+ label: node,
+ type: "path", // It's a path node since we're building the .class file
+ neighbor: [javaFilePath], // The corresponding .java file
+ build: { node, neighbor ->
+ def javac_cmd = "${JAVA_COMP_FP} -d ${dir_map.JAVA_COMP_OUT} -sourcepath ${dir_map.JAVA_COMP_IN_DL} ${neighbor[0]}"
+ javac_cmd.execute().waitFor()
+ }
+ ]
+}
+
+// given .jar returns node to build .jar from .class
+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
+]
+
+// comprehensions to make regexprs more readable
+def base = "[a-zA-Z0-9_-]+"
+def ext = "[a-zA-Z0-9_-]+$"
+def name = "${base}\\.${ext}"
+def path = ".+/${name}"
+
+
+
+// LocalWords: wellformed
diff --git a/developer/document/cycle_detection.html b/developer/document/cycle_detection.html
new file mode 100644
index 0000000..3c9fb85
--- /dev/null
+++ b/developer/document/cycle_detection.html
@@ -0,0 +1,105 @@
+
+
+
+ Dependency Build Algorithm
+
+
+
+ Cycle Detection in Dependency Graph
+ Overview
+
+ 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.
+
+
+
diff --git a/developer/document/dependency_graph.html b/developer/document/dependency_graph.html
new file mode 100644
index 0000000..378c52d
--- /dev/null
+++ b/developer/document/dependency_graph.html
@@ -0,0 +1,137 @@
+
+
+
+ Dependency Build Algorithm
+
+
+
+ Cycle Detection in Dependency Graph
+ Overview
+
+ 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.
+
+