first pass converted to Java
authorThomas Walker Lynch <xtujpz@reasoningtechnology.com>
Tue, 8 Oct 2024 04:57:07 +0000 (04:57 +0000)
committerThomas Walker Lynch <xtujpz@reasoningtechnology.com>
Tue, 8 Oct 2024 04:57:07 +0000 (04:57 +0000)
developer/deprecated/groovyc/#AriadneGraph.groovy# [new file with mode: 0644]
developer/deprecated/groovyc/AriadneGraph.groovy [new file with mode: 0644]
developer/deprecated/groovyc/BuildGraph.groovy [new file with mode: 0644]
developer/document/nomenclature.txt [new file with mode: 0644]
developer/executable/make.sh
developer/groovyc/AriadneGraph.groovy [deleted file]
developer/groovyc/BuildGraph.groovy [deleted file]
developer/javac/AriadneGraph.java [new file with mode: 0644]
developer/jvm/Ariadne.jar [deleted file]
tester/test2/TestGraph.groovy

diff --git a/developer/deprecated/groovyc/#AriadneGraph.groovy# b/developer/deprecated/groovyc/#AriadneGraph.groovy#
new file mode 100644 (file)
index 0000000..e9d8f2b
--- /dev/null
@@ -0,0 +1,632 @@
+import java.nio.file.Files
+import java.nio.file.Paths
+
+class AriadneGraph {
+
+  //--------------------------------------------------------------------------------
+  // instance data
+  //
+
+  static Boolean debug = true
+  Map node_map = [:]
+  List node_f_list = []
+
+
+  AriadneGraph(Map node_map ,List node_f_list){
+    def accept_arg_list = true;
+    if( !(node_map === null) && !(node_map instanceof Map) ) accept_arg_list = false
+    if( !(node_f_list === null) && !(node_f_list instanceof List) ) accept_arg_list = false
+    if(node_map === null && node_f_list === null) accept_arg_list = false
+    if(!accept_arg_list){
+      println "AriandreGraph: requiers one or both of 'node_map' as Map, and 'node_f_list as List.'"
+      System.exit(1)
+    }
+    this.node_map = node_map ?: [:]
+    this.node_f_list = node_f_list ?: []
+  }
+
+  //--------------------------------------------------------------------------------
+  // File utility functions
+
+  public static Map<String ,String> unpack_file_path(String file_fp){
+    if( debug ) System.out.println("unpack_file_path::file_fp: " + file_fp);
+
+    File file = new File(file_fp);
+    String parent_dp = (file.getParent() != null) ? file.getParent() : "";
+
+    if( !parent_dp.isEmpty() && !parent_dp.endsWith(File.separator) ){
+      parent_dp += File.separator;
+    }
+
+    String file_fn = file.getName();
+    String file_fn_base = file_fn;
+    String file_fn_ext = "";
+
+    int last_index = file_fn.lastIndexOf('.');
+    if( last_index > 0 ){
+      file_fn_base = file_fn.substring(0 ,last_index);
+      if( last_index + 1 < file_fn.length() ){
+        file_fn_ext = file_fn.substring(last_index + 1);
+      }
+    }
+
+    Map<String ,String> ret_val = new HashMap<>();
+    ret_val.put("dp" ,parent_dp);
+    ret_val.put("fn" ,file_fn);
+    ret_val.put("fn_base" ,file_fn_base);
+    ret_val.put("fn_ext" ,file_fn_ext);
+
+    if( debug ) System.out.println("unpack_file_path::ret_val: " + ret_val);
+
+    return ret_val;
+  }
+
+  public static boolean file_exists_q(String node_label){
+    Path node_path = Paths.get(node_label);
+    return Files.exists(node_path);
+  }
+}
+
+
+  /*--------------------------------------------------------------------------------
+   File utility functions
+  */
+  static Map unpack_file_path(String file_fp) {
+    if (debug) println("unpack_file_path::file_fp: ${file_fp}")
+
+    def file = new File(file_fp)
+    def parent_dp = file.getParent() ?: ""
+
+    if (parent_dp && !parent_dp.endsWith(File.separator)) {
+      parent_dp += File.separator
+    }
+
+    def file_fn = file.getName()
+    def file_fn_base = file_fn
+    def file_fn_ext = ''
+
+    if (file_fn.lastIndexOf('.') > 0) {
+      file_fn_base = file_fn[0..file_fn.lastIndexOf('.') - 1]
+      if (file_fn.lastIndexOf('.') + 1 < file_fn.length()) {
+        file_fn_ext = file_fn[file_fn.lastIndexOf('.') + 1..-1]
+      }
+    }
+
+    def ret_val = [
+      dp      : parent_dp,
+      fn      : file_fn,
+      fn_base : file_fn_base,
+      fn_ext  : file_fn_ext
+    ]
+    if (debug) println("unpack_file_path::ret_val: ${ret_val}")
+
+    return ret_val
+  }
+
+  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'  // label is a symbol
+    ,'path'   // label is a path to a file, though it might not exist
+    ,'leaf'   // label is a path to a file that has no dependencies
+    ,'generator' // label is a path, but node has no neighbors
+    ,'error'   // typically created by the system node has a message property
+  ] as Set
+
+  static Set persistent_node_mark_set = 
+    [
+    'cycle_member' 
+     ,'wellformed' 
+     ,'build_failed'
+     ,'null_node'
+     ] 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'
+    ,'neighbor_label_not_in_graph'
+    ,'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 << 'null_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
+  }
+
+  /*--------------------------------------------------------------------------------
+   A well formed graph checker.  Traverses entire graph and marks nodes
+   that are not well formed or that are part of a cycle.
+
+   This must be run on the graph for `lookup_marked_good` to work.
+  */
+
+  def mark_node_form(node ,verbose = true){
+    if(debug){
+      if(node)
+        println("mark_node_form::node: ${node}")
+      else
+        println("mark_node_form given a null node")
+    }
+    
+    def form_errors = wellformed_q(node)
+    if( form_errors.isEmpty() ){
+      set_mark(node ,'wellformed');
+      return 'wellformed'
+    }
+    // at this point we know that form_errors is not empty
+    
+    if(verbose){
+      if(node && node.label && 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("")
+    }
+
+    return 'malformed'
+  }
+
+
+  /* 
+   Each node_label must be a string and not empty.
+
+   Subleties here because we have not yet determined if the nodes we are
+   wellformed (after all, that is what we are determining here).
+
+   Given a path stack initialized with the path root ,descends to a leaf node
+   while looking for cycles. Marks nodes as to their form.  Returns a set of
+   tokens.
+
+   If we want to attempt to build 'islands' of things that might be located on
+   the far side of cycles, then modify the cycle finder to return a list of
+   cycles (i.e. a list of lists), then use each of cycle definition (a list) as
+   the root nodes for further search.
+
+
+   */
+  static Set markup_graph_f_descend_set = [
+    'empty_path_stack'
+    ,'cycle_found'
+    ,'undefined_node'
+    ,'exists_malformed'
+    ,'defacto_leaf'
+  ] as Set
+
+  def markup_graph_f_descend(path_stack ,boolean verbose = true){
+    def ret_value = [] as Set
+    if( path_stack.isEmpty() ){
+      if(verbose) println( "markup_graph_f_descend:: given empty path_stack to descend from")
+      ret_value << 'empty_path_stack'
+      return ret_value
+    }
+    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, if found marks cycle members
+      if( local_path.size() > 1){
+        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 ->
+            if(verbose) print " ${cycle_node_label}"
+            def cycle_node = lookup(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
+        }
+      }
+
+      def local_node = lookup(local_node_label)
+      if( !local_node ){
+        ret_value << 'undefined_node' 
+        return ret_value
+      }
+      if( mark_node_form(local_node) == 'malformed' ){
+        ret_value << 'exists_malformed'
+      }
+      if( local_node.neighbor.isEmpty() ){
+        ret_value << 'defacto_leaf' // might not be `type:leaf`
+        return ret_value
+      }
+
+      // 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)
+  }
+
+  /*
+   Given root_node_label_list ,marks up the graph and returns a set possibly
+   containing 'all_wellformed' and 'cycles_exist'.
+
+   Marks potentially added to each node include  'cycle_member' ,'wellformed'.
+   Note that these marks are independent.
+  */
+  def wellformed_graph_q(root_node_label_list ,boolean verbose = true){
+    def ret_value = [] as Set
+    def exists_malformed = false;
+    def result // used variously
+
+    if( root_node_label_list.isEmpty() ) return ret_value
+    
+    // Initialize the DFS tree iterator.
+    def path_stack = []
+    path_stack << root_node_label_list.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{
+      result = markup_graph_f_descend(path_stack ,verbose)
+      if('cycle_found' in result) ret_value << 'cycle_exists'
+      if('undefined_node' in result) exists_malformed = true;
+      if('exists_malformed' in result) exists_malformed = true;
+
+      // 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())
+
+    if(!exists_malformed) ret_value << 'all_wellformed'
+    if( verbose ){
+      if(exists_malformed) println("one or more malformed nodes were found")
+      def exists_cycle = 'cycle_found' in ret_value
+      if(exists_cycle) println("one or more cyclic dependency loop found")
+      if( exists_malformed || exists_cycle ) println("will attempt to build unaffected nodes")
+    }
+
+    return ret_value
+  }
+
+  /*--------------------------------------------------------------------------------
+     Graph traversal
+  */
+
+  // given a node label, looks it up on the dependency graph, returns the node or null
+  Map lookup(String node_label ,boolean verbose = true){
+
+    if(!node_label){
+      if(verbose) println("lookup:: given node_label is null or an empty string")
+      return null
+    }
+
+    // try the map
+    def node = this.node_map[node_label]
+    if(node){
+      node.label = node_label
+      if(verbose) println("lookup:: found from map: ${node}")
+      return node
+    }
+    // at this point node will be null
+
+    // The map lookup failed, lets try the function recognizer list ..
+    def match_result
+    for( func in this.node_f_list ){
+      match_result = func(node_label)
+      if( match_result.status == 'matched' ){
+        node = match_result
+        break
+      }
+    }
+
+    if(verbose)
+      if(node) println("lookup:: found from recognizer function: ${node}")
+      else println("lookup:: failed to find label: ${node_label}")
+
+    return node
+  }
+
+  // mark aware lookup function
+  def lookup_marked_good(node_label ,verbose = true){
+    def node = lookup(node_label ,verbose)
+    if( node && marked_good_q(node) ) return node;
+    return null;
+  }
+
+
+  /*
+   Given `root_node_label_list` 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_label_list ,node_function ,boolean verbose = true) {
+    if(verbose) println("all_DAG_DF::")
+
+    def error_token_set = [] as Set
+
+    def accept_arg_list = true
+    if( !node_function ){
+      error_token_set << 'null_node_function'
+      accept_arg_list = false
+    }
+    if( !(node_function instanceof Closure) ){
+      error_token_set << 'nod_function_not_a_function'
+      accept_arg_list = false
+    }
+    if( !root_node_label_list  ){
+      error_token_set << 'null_root_node_label_list'
+      accept_arg_list = false
+    }
+    if( root_node_label_list.isEmpty() ){
+      error_token_set << 'empty_root_node_label_list'
+      accept_arg_list = false
+    }
+    if( !accept_arg_list ) return error_token_set
+
+    def visited = [] as Set
+    def in_traversal_order = []
+
+    def stack = []
+    root_node_label_list.each { root_label ->
+      stack << root_label
+    }
+
+    do {
+      if( stack.isEmpty() ) break
+      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(true)
+
+    in_traversal_order.reverse().each { node ->
+      node_function(node ,error_token_set)
+    }
+
+    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
+  }
+
+  void run_build_scripts_f( List root_node_label_list ,boolean verbose = true ){
+
+    if( root_node_label_list.isEmpty() ) return
+    Set error_token_set // used to catch return values
+
+    println( "run_build_script:: Checking if graph is well formed." )
+    error_token_set = wellformed_graph_q(root_node_label_list)
+    if( error_token_set && !error_token_set.isEmpty() ){
+      println( "Graph is not well-formed. Expect build problems. Errors:" )
+      error_token_set.each { token ->
+        println( "  - ${token}" )
+      }
+    } else {
+      println( "Graph is well-formed. Proceeding with build." )
+    }
+
+    def node_function = { node ,error_token_set_2 ->
+
+      if( !can_be_built_q( node ) ){
+        println( "run_build_scripts_f:: Skipping build for ${node.label} due to problems with dependencies." )
+        return
+      }
+      if( !should_be_built_q( node ) ){
+        if( verbose ) println( "run_build_scripts_f:: ${node.label} already up to date" )
+        return
+      }
+
+      // build the target
+      println( "run_build_scripts_f:: Running build script for ${node.label}" )
+      node.build()
+
+      // for path nodes, check if the build updated the target at path
+      if( node.type == 'path' && should_be_built_q( node ) ){
+        println( "run_build_scripts_f:: Build failed for ${node.label}" )
+        set_mark(node ,'build_failed')
+      }
+
+    }
+
+    println("run_build_scripts_f:: running ...")
+    error_token_set = all_DAG_DF(root_node_label_list, node_function, verbose)
+    if( error_token_set ){
+      error_token_set.each { error ->
+        println("run_build_scripts_f::all_DAG_DF:: ${error}")
+      }
+   }
+
+  }
+
+
+}
+
+
+/*
+ def clean(nodes_to_clean) {
+  def all_dependencies = this.node_map["all"].neighbor.clone()
+  nodes_to_clean.each { node ->
+    all_dependencies.remove(node)
+  }
+
+  def must_have_nodes = []
+  all_dependencies.each { node ->
+    def node_info = this.node_map[node]
+    if (node_info.must_have) {
+      must_have_nodes += node_info.must_have
+    }
+  }
+
+  def to_clean_list = []
+  nodes_to_clean.each { node ->
+    if (!must_have_nodes.contains(node) && this.node_map[node].type == "path") {
+      to_clean_list += node
+    }
+  }
+
+  to_clean_list.each { node ->
+    def file_path = this.node_map[node].label
+    def file = new File(file_path)
+    if (file.exists()) {
+      file.delete()
+      println "Deleted file: ${file_path}"
+    }
+  }
+}
+*/
diff --git a/developer/deprecated/groovyc/AriadneGraph.groovy b/developer/deprecated/groovyc/AriadneGraph.groovy
new file mode 100644 (file)
index 0000000..e149d23
--- /dev/null
@@ -0,0 +1,566 @@
+import java.nio.file.Files
+import java.nio.file.Paths
+
+class AriadneGraph {
+
+  static Boolean debug = true
+  Map node_map = [:]
+  List node_f_list = []
+
+  AriadneGraph(Map node_map ,List node_f_list){
+    def accept_arg_list = true;
+    if( !(node_map === null) && !(node_map instanceof Map) ) accept_arg_list = false
+    if( !(node_f_list === null) && !(node_f_list instanceof List) ) accept_arg_list = false
+    if(node_map === null && node_f_list === null) accept_arg_list = false
+    if(!accept_arg_list){
+      println "AriandreGraph: requiers one or both of 'node_map' as Map, and 'node_f_list as List.'"
+      System.exit(1)
+    }
+    this.node_map = node_map ?: [:]
+    this.node_f_list = node_f_list ?: []
+  }
+
+  /*--------------------------------------------------------------------------------
+   File utility functions
+  */
+  static Map unpack_file_path(String file_fp) {
+    if (debug) println("unpack_file_path::file_fp: ${file_fp}")
+
+    def file = new File(file_fp)
+    def parent_dp = file.getParent() ?: ""
+
+    if (parent_dp && !parent_dp.endsWith(File.separator)) {
+      parent_dp += File.separator
+    }
+
+    def file_fn = file.getName()
+    def file_fn_base = file_fn
+    def file_fn_ext = ''
+
+    if (file_fn.lastIndexOf('.') > 0) {
+      file_fn_base = file_fn[0..file_fn.lastIndexOf('.') - 1]
+      if (file_fn.lastIndexOf('.') + 1 < file_fn.length()) {
+        file_fn_ext = file_fn[file_fn.lastIndexOf('.') + 1..-1]
+      }
+    }
+
+    def ret_val = [
+      dp      : parent_dp,
+      fn      : file_fn,
+      fn_base : file_fn_base,
+      fn_ext  : file_fn_ext
+    ]
+    if (debug) println("unpack_file_path::ret_val: ${ret_val}")
+
+    return ret_val
+  }
+
+  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'  // label is a symbol
+    ,'path'   // label is a path to a file, though it might not exist
+    ,'leaf'   // label is a path to a file that has no dependencies
+    ,'generator' // label is a path, but node has no neighbors
+    ,'error'   // typically created by the system node has a message property
+  ] as Set
+
+  static Set persistent_node_mark_set = 
+    [
+    'cycle_member' 
+     ,'wellformed' 
+     ,'build_failed'
+     ,'null_node'
+     ] 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'
+    ,'neighbor_label_not_in_graph'
+    ,'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 << 'null_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
+  }
+
+  /*--------------------------------------------------------------------------------
+   A well formed graph checker.  Traverses entire graph and marks nodes
+   that are not well formed or that are part of a cycle.
+
+   This must be run on the graph for `lookup_marked_good` to work.
+  */
+
+  def mark_node_form(node ,verbose = true){
+    if(debug){
+      if(node)
+        println("mark_node_form::node: ${node}")
+      else
+        println("mark_node_form given a null node")
+    }
+    
+    def form_errors = wellformed_q(node)
+    if( form_errors.isEmpty() ){
+      set_mark(node ,'wellformed');
+      return 'wellformed'
+    }
+    // at this point we know that form_errors is not empty
+    
+    if(verbose){
+      if(node && node.label && 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("")
+    }
+
+    return 'malformed'
+  }
+
+
+  static Set markup_graph_f_descend_set = [
+    'empty_path_stack'
+    ,'cycle_found'
+    ,'undefined_node'
+    ,'exists_malformed'
+    ,'defacto_leaf'
+  ] as Set
+  def markup_graph_f_descend(path_stack ,boolean verbose = true){
+    def ret_value = [] as Set
+    if( path_stack.isEmpty() ){
+      if(verbose) println( "markup_graph_f_descend:: given empty path_stack to descend from")
+      ret_value << 'empty_path_stack'
+      return ret_value
+    }
+    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, if found marks cycle members
+      if( local_path.size() > 1){
+        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 ->
+            if(verbose) print " ${cycle_node_label}"
+            def cycle_node = lookup(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
+        }
+      }
+
+      def local_node = lookup(local_node_label)
+      if( !local_node ){
+        ret_value << 'undefined_node' 
+        return ret_value
+      }
+      if( mark_node_form(local_node) == 'malformed' ){
+        ret_value << 'exists_malformed'
+      }
+      if( local_node.neighbor.isEmpty() ){
+        ret_value << 'defacto_leaf' // might not be `type:leaf`
+        return ret_value
+      }
+
+      // 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)
+  }
+
+  /*
+   Given root_node_label_list ,marks up the graph and returns a set possibly
+   containing 'all_wellformed' and 'cycles_exist'.
+
+   Marks potentially added to each node include  'cycle_member' ,'wellformed'.
+   Note that these marks are independent.
+  */
+  def wellformed_graph_q(root_node_label_list ,boolean verbose = true){
+    def ret_value = [] as Set
+    def exists_malformed = false;
+    def result // used variously
+
+    if( root_node_label_list.isEmpty() ) return ret_value
+    
+    // Initialize the DFS tree iterator.
+    def path_stack = []
+    path_stack << root_node_label_list.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{
+      result = markup_graph_f_descend(path_stack ,verbose)
+      if('cycle_found' in result) ret_value << 'cycle_exists'
+      if('undefined_node' in result) exists_malformed = true;
+      if('exists_malformed' in result) exists_malformed = true;
+
+      // 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())
+
+    if(!exists_malformed) ret_value << 'all_wellformed'
+    if( verbose ){
+      if(exists_malformed) println("one or more malformed nodes were found")
+      def exists_cycle = 'cycle_found' in ret_value
+      if(exists_cycle) println("one or more cyclic dependency loop found")
+      if( exists_malformed || exists_cycle ) println("will attempt to build unaffected nodes")
+    }
+
+    return ret_value
+  }
+
+  /*--------------------------------------------------------------------------------
+     Graph traversal
+  */
+
+  // given a node label, looks it up on the dependency graph, returns the node or null
+  Map lookup(String node_label ,boolean verbose = true){
+
+    if(!node_label){
+      if(verbose) println("lookup:: given node_label is null or an empty string")
+      return null
+    }
+
+    // try the map
+    def node = this.node_map[node_label]
+    if(node){
+      node.label = node_label
+      if(verbose) println("lookup:: found from map: ${node}")
+      return node
+    }
+    // at this point node will be null
+
+    // The map lookup failed, lets try the function recognizer list ..
+    def match_result
+    for( func in this.node_f_list ){
+      match_result = func(node_label)
+      if( match_result.status == 'matched' ){
+        node = match_result
+        break
+      }
+    }
+
+    if(verbose)
+      if(node) println("lookup:: found from recognizer function: ${node}")
+      else println("lookup:: failed to find label: ${node_label}")
+
+    return node
+  }
+
+  // mark aware lookup function
+  def lookup_marked_good(node_label ,verbose = true){
+    def node = lookup(node_label ,verbose)
+    if( node && marked_good_q(node) ) return node;
+    return null;
+  }
+
+
+  /*
+   Given `root_node_label_list` 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_label_list ,node_function ,boolean verbose = true) {
+    if(verbose) println("all_DAG_DF::")
+
+    def error_token_set = [] as Set
+
+    def accept_arg_list = true
+    if( !node_function ){
+      error_token_set << 'null_node_function'
+      accept_arg_list = false
+    }
+    if( !(node_function instanceof Closure) ){
+      error_token_set << 'nod_function_not_a_function'
+      accept_arg_list = false
+    }
+    if( !root_node_label_list  ){
+      error_token_set << 'null_root_node_label_list'
+      accept_arg_list = false
+    }
+    if( root_node_label_list.isEmpty() ){
+      error_token_set << 'empty_root_node_label_list'
+      accept_arg_list = false
+    }
+    if( !accept_arg_list ) return error_token_set
+
+    def visited = [] as Set
+    def in_traversal_order = []
+
+    def stack = []
+    root_node_label_list.each { root_label ->
+      stack << root_label
+    }
+
+    do {
+      if( stack.isEmpty() ) break
+      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(true)
+
+    in_traversal_order.reverse().each { node ->
+      node_function(node ,error_token_set)
+    }
+
+    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
+  }
+
+  void run_build_scripts_f( List root_node_label_list ,boolean verbose = true ){
+
+    if( root_node_label_list.isEmpty() ) return
+    Set error_token_set // used to catch return values
+
+    println( "run_build_script:: Checking if graph is well formed." )
+    error_token_set = wellformed_graph_q(root_node_label_list)
+    if( error_token_set && !error_token_set.isEmpty() ){
+      println( "Graph is not well-formed. Expect build problems. Errors:" )
+      error_token_set.each { token ->
+        println( "  - ${token}" )
+      }
+    } else {
+      println( "Graph is well-formed. Proceeding with build." )
+    }
+
+    def node_function = { node ,error_token_set_2 ->
+
+      if( !can_be_built_q( node ) ){
+        println( "run_build_scripts_f:: Skipping build for ${node.label} due to problems with dependencies." )
+        return
+      }
+      if( !should_be_built_q( node ) ){
+        if( verbose ) println( "run_build_scripts_f:: ${node.label} already up to date" )
+        return
+      }
+
+      // build the target
+      println( "run_build_scripts_f:: Running build script for ${node.label}" )
+      node.build()
+
+      // for path nodes, check if the build updated the target at path
+      if( node.type == 'path' && should_be_built_q( node ) ){
+        println( "run_build_scripts_f:: Build failed for ${node.label}" )
+        set_mark(node ,'build_failed')
+      }
+
+    }
+
+    println("run_build_scripts_f:: running ...")
+    error_token_set = all_DAG_DF(root_node_label_list, node_function, verbose)
+    if( error_token_set ){
+      error_token_set.each { error ->
+        println("run_build_scripts_f::all_DAG_DF:: ${error}")
+      }
+   }
+
+  }
+
+
+}
+
+
+/*
+ def clean(nodes_to_clean) {
+  def all_dependencies = this.node_map["all"].neighbor.clone()
+  nodes_to_clean.each { node ->
+    all_dependencies.remove(node)
+  }
+
+  def must_have_nodes = []
+  all_dependencies.each { node ->
+    def node_info = this.node_map[node]
+    if (node_info.must_have) {
+      must_have_nodes += node_info.must_have
+    }
+  }
+
+  def to_clean_list = []
+  nodes_to_clean.each { node ->
+    if (!must_have_nodes.contains(node) && this.node_map[node].type == "path") {
+      to_clean_list += node
+    }
+  }
+
+  to_clean_list.each { node ->
+    def file_path = this.node_map[node].label
+    def file = new File(file_path)
+    if (file.exists()) {
+      file.delete()
+      println "Deleted file: ${file_path}"
+    }
+  }
+}
+*/
diff --git a/developer/deprecated/groovyc/BuildGraph.groovy b/developer/deprecated/groovyc/BuildGraph.groovy
new file mode 100644 (file)
index 0000000..c68fa43
--- /dev/null
@@ -0,0 +1,58 @@
+class BuildGraph {
+
+  // Function to load the graph class dynamically
+  static def include_a_class(String a_class_fp) {
+    def class_loader = BuildGraph.class.classLoader
+    def class_name = a_class_fp.replace('/', '.').replace('.class', '')
+    try {
+      return class_loader.loadClass(class_name)
+    } catch (Exception e) {
+      println "Error loading class '${class_name}': ${e.message}"
+      return null
+    }
+  }
+
+  // Build function
+  static def build(String graph_definition_fp, List<String> root_node_labels) {
+
+    // Print summary of what we are doing
+    println "build:: Building targets for graph '${graph_definition_fp}.class'"
+    if (root_node_labels.isEmpty()) {
+      println "No build targets specified. Please provide root node labels to build."
+      System.exit(0)
+    }
+    println "Building targets: ${root_node_labels.join(', ')}"
+
+    // Load the dependency graph class from arg[1]
+    def graph_definition_class = include_a_class(graph_definition_fp)
+    if (graph_definition_class) {
+      println "build:: loaded ${graph_definition_fp}.class"
+    } else {
+      println "build:: failed to load ${graph_definition_fp}.class"
+      System.exit(1)
+    }
+
+    // Get the node_map and node_f_list from the graph class
+    def node_map = graph_definition_class.get_node_map()
+    def node_f_list = graph_definition_class.get_node_f_list()
+    println "node_map: ${node_map}"
+    println "node_f_list: ${node_f_list}"
+
+    // Create an instance of AriadneGraph, and run the build scripts
+    def graph = new AriadneGraph(node_map, node_f_list)
+    graph.run_build_scripts_f(root_node_labels)
+  }
+
+  // Entry point when run as a script
+  static void main(String[] args) {
+    if (args.length == 0) {
+      println "Usage: ./build <graph_definition.class> [root_node_labels...]"
+      System.exit(1)
+    }
+
+    // Get graph definition file and root node labels
+    def graph_definition_fp = args[0]
+    def root_node_labels = args.length > 1 ? args[1..-1] : []
+    build(graph_definition_fp, root_node_labels)
+  }
+}
diff --git a/developer/document/nomenclature.txt b/developer/document/nomenclature.txt
new file mode 100644 (file)
index 0000000..633dc4a
--- /dev/null
@@ -0,0 +1,3 @@
+
+wellformed is a single word. Its antonym is 'malformed'. Wellformed syntax
+parses without errors.
index fe471b5..37ff783 100755 (executable)
@@ -15,7 +15,6 @@ rm -rf jvm/*
 
 # Compile all files
 echo "Compiling files..."
-groovyc groovyc/*.groovy -d scratch_pad
 javac javac/*.java -d scratch_pad
 
 if [ $? -ne 0 ]; then
diff --git a/developer/groovyc/AriadneGraph.groovy b/developer/groovyc/AriadneGraph.groovy
deleted file mode 100644 (file)
index 9ea9b41..0000000
+++ /dev/null
@@ -1,588 +0,0 @@
-import java.nio.file.Files
-import java.nio.file.Paths
-
-class AriadneGraph {
-
-  // to turn on debug checks and messages
-  static Boolean debug = true
-
-  // Instance variables for graph data
-  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){
-    def accept_arg_list = true;
-    if( !(node_map === null) && !(node_map instanceof Map) ) accept_arg_list = false
-    if( !(node_f_list === null) && !(node_f_list instanceof List) ) accept_arg_list = false
-    if(node_map === null && node_f_list === null) accept_arg_list = false
-    if(!accept_arg_list){
-      println "AriandreGraph: requiers one or both of 'node_map' as Map, and 'node_f_list as List.'"
-      System.exit(1)
-    }
-    this.node_map = node_map ?: [:]
-    this.node_f_list = node_f_list ?: []
-  }
-
-  /*--------------------------------------------------------------------------------
-   File utility functions
-  */
-  static Map unpack_file_path(String file_fp) {
-    if (debug) println("unpack_file_path::file_fp: ${file_fp}")
-
-    def file = new File(file_fp)
-    def parent_dp = file.getParent() ?: ""
-
-    if (parent_dp && !parent_dp.endsWith(File.separator)) {
-      parent_dp += File.separator
-    }
-
-    def file_fn = file.getName()
-    def file_fn_base = file_fn
-    def file_fn_ext = ''
-
-    if (file_fn.lastIndexOf('.') > 0) {
-      file_fn_base = file_fn[0..file_fn.lastIndexOf('.') - 1]
-      if (file_fn.lastIndexOf('.') + 1 < file_fn.length()) {
-        file_fn_ext = file_fn[file_fn.lastIndexOf('.') + 1..-1]
-      }
-    }
-
-    def ret_val = [
-      dp      : parent_dp,
-      fn      : file_fn,
-      fn_base : file_fn_base,
-      fn_ext  : file_fn_ext
-    ]
-    if (debug) println("unpack_file_path::ret_val: ${ret_val}")
-
-    return ret_val
-  }
-
-  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'  // label is a symbol
-    ,'path'   // label is a path to a file, though it might not exist
-    ,'leaf'   // label is a path to a file that has no dependencies
-    ,'generator' // label is a path, but node has no neighbors
-    ,'error'   // typically created by the system node has a message property
-  ] as Set
-
-  static Set persistent_node_mark_set = 
-    [
-    'cycle_member' 
-     ,'wellformed' 
-     ,'build_failed'
-     ,'null_node'
-     ] 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'
-    ,'neighbor_label_not_in_graph'
-    ,'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 << 'null_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
-  }
-
-  /*--------------------------------------------------------------------------------
-   A well formed graph checker.  Traverses entire graph and marks nodes
-   that are not well formed or that are part of a cycle.
-
-   This must be run on the graph for `lookup_marked_good` to work.
-  */
-
-  def mark_node_form(node ,verbose = true){
-    if(debug){
-      if(node)
-        println("mark_node_form::node: ${node}")
-      else
-        println("mark_node_form given a null node")
-    }
-    
-    def form_errors = wellformed_q(node)
-    if( form_errors.isEmpty() ){
-      set_mark(node ,'wellformed');
-      return 'wellformed'
-    }
-    // at this point we know that form_errors is not empty
-    
-    if(verbose){
-      if(node && node.label && 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("")
-    }
-
-    return 'malformed'
-  }
-
-
-  /* 
-   Each node_label must be a string and not empty.
-
-   Subleties here because we have not yet determined if the nodes we are
-   wellformed (after all, that is what we are determining here).
-
-   Given a path stack initialized with the path root ,descends to a leaf node
-   while looking for cycles. Marks nodes as to their form.  Returns a set of
-   tokens.
-
-   If we want to attempt to build 'islands' of things that might be located on
-   the far side of cycles, then modify the cycle finder to return a list of
-   cycles (i.e. a list of lists), then use each of cycle definition (a list) as
-   the root nodes for further search.
-
-
-   */
-  static Set markup_graph_f_descend_set = [
-    'empty_path_stack'
-    ,'cycle_found'
-    ,'undefined_node'
-    ,'exists_malformed'
-    ,'defacto_leaf'
-  ] as Set
-
-  def markup_graph_f_descend(path_stack ,boolean verbose = true){
-    def ret_value = [] as Set
-    if( path_stack.isEmpty() ){
-      if(verbose) println( "markup_graph_f_descend:: given empty path_stack to descend from")
-      ret_value << 'empty_path_stack'
-      return ret_value
-    }
-    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, if found marks cycle members
-      if( local_path.size() > 1){
-        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 ->
-            if(verbose) print " ${cycle_node_label}"
-            def cycle_node = lookup(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
-        }
-      }
-
-      def local_node = lookup(local_node_label)
-      if( !local_node ){
-        ret_value << 'undefined_node' 
-        return ret_value
-      }
-      if( mark_node_form(local_node) == 'malformed' ){
-        ret_value << 'exists_malformed'
-      }
-      if( local_node.neighbor.isEmpty() ){
-        ret_value << 'defacto_leaf' // might not be `type:leaf`
-        return ret_value
-      }
-
-      // 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)
-  }
-
-  /*
-   Given root_node_label_list ,marks up the graph and returns a set possibly
-   containing 'all_wellformed' and 'cycles_exist'.
-
-   Marks potentially added to each node include  'cycle_member' ,'wellformed'.
-   Note that these marks are independent.
-  */
-  def wellformed_graph_q(root_node_label_list ,boolean verbose = true){
-    def ret_value = [] as Set
-    def exists_malformed = false;
-    def result // used variously
-
-    if( root_node_label_list.isEmpty() ) return ret_value
-    
-    // Initialize the DFS tree iterator.
-    def path_stack = []
-    path_stack << root_node_label_list.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{
-      result = markup_graph_f_descend(path_stack ,verbose)
-      if('cycle_found' in result) ret_value << 'cycle_exists'
-      if('undefined_node' in result) exists_malformed = true;
-      if('exists_malformed' in result) exists_malformed = true;
-
-      // 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())
-
-    if(!exists_malformed) ret_value << 'all_wellformed'
-    if( verbose ){
-      if(exists_malformed) println("one or more malformed nodes were found")
-      def exists_cycle = 'cycle_found' in ret_value
-      if(exists_cycle) println("one or more cyclic dependency loop found")
-      if( exists_malformed || exists_cycle ) println("will attempt to build unaffected nodes")
-    }
-
-    return ret_value
-  }
-
-  /*--------------------------------------------------------------------------------
-     Graph traversal
-  */
-
-  // given a node label, looks it up on the dependency graph, returns the node or null
-  Map lookup(String node_label ,boolean verbose = true){
-
-    if(!node_label){
-      if(verbose) println("lookup:: given node_label is null or an empty string")
-      return null
-    }
-
-    // try the map
-    def node = this.node_map[node_label]
-    if(node){
-      node.label = node_label
-      if(verbose) println("lookup:: found from map: ${node}")
-      return node
-    }
-    // at this point node will be null
-
-    // The map lookup failed, lets try the function recognizer list ..
-    def match_result
-    for( func in this.node_f_list ){
-      match_result = func(node_label)
-      if( match_result.status == 'matched' ){
-        node = match_result
-        break
-      }
-    }
-
-    if(verbose)
-      if(node) println("lookup:: found from recognizer function: ${node}")
-      else println("lookup:: failed to find label: ${node_label}")
-
-    return node
-  }
-
-  // mark aware lookup function
-  def lookup_marked_good(node_label ,verbose = true){
-    def node = lookup(node_label ,verbose)
-    if( node && marked_good_q(node) ) return node;
-    return null;
-  }
-
-
-  /*
-   Given `root_node_label_list` 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_label_list ,node_function ,boolean verbose = true) {
-    if(verbose) println("all_DAG_DF::")
-
-    def error_token_set = [] as Set
-
-    def accept_arg_list = true
-    if( !node_function ){
-      error_token_set << 'null_node_function'
-      accept_arg_list = false
-    }
-    if( !(node_function instanceof Closure) ){
-      error_token_set << 'nod_function_not_a_function'
-      accept_arg_list = false
-    }
-    if( !root_node_label_list  ){
-      error_token_set << 'null_root_node_label_list'
-      accept_arg_list = false
-    }
-    if( root_node_label_list.isEmpty() ){
-      error_token_set << 'empty_root_node_label_list'
-      accept_arg_list = false
-    }
-    if( !accept_arg_list ) return error_token_set
-
-    def visited = [] as Set
-    def in_traversal_order = []
-
-    def stack = []
-    root_node_label_list.each { root_label ->
-      stack << root_label
-    }
-
-    do {
-      if( stack.isEmpty() ) break
-      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(true)
-
-    in_traversal_order.reverse().each { node ->
-      node_function(node ,error_token_set)
-    }
-
-    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
-  }
-
-  void run_build_scripts_f( List root_node_label_list ,boolean verbose = true ){
-
-    if( root_node_label_list.isEmpty() ) return
-    Set error_token_set // used to catch return values
-
-    println( "run_build_script:: Checking if graph is well formed." )
-    error_token_set = wellformed_graph_q(root_node_label_list)
-    if( error_token_set && !error_token_set.isEmpty() ){
-      println( "Graph is not well-formed. Expect build problems. Errors:" )
-      error_token_set.each { token ->
-        println( "  - ${token}" )
-      }
-    } else {
-      println( "Graph is well-formed. Proceeding with build." )
-    }
-
-    def node_function = { node ,error_token_set_2 ->
-
-      if( !can_be_built_q( node ) ){
-        println( "run_build_scripts_f:: Skipping build for ${node.label} due to problems with dependencies." )
-        return
-      }
-      if( !should_be_built_q( node ) ){
-        if( verbose ) println( "run_build_scripts_f:: ${node.label} already up to date" )
-        return
-      }
-
-      // build the target
-      println( "run_build_scripts_f:: Running build script for ${node.label}" )
-      node.build()
-
-      // for path nodes, check if the build updated the target at path
-      if( node.type == 'path' && should_be_built_q( node ) ){
-        println( "run_build_scripts_f:: Build failed for ${node.label}" )
-        set_mark(node ,'build_failed')
-      }
-
-    }
-
-    println("run_build_scripts_f:: running ...")
-    error_token_set = all_DAG_DF(root_node_label_list, node_function, verbose)
-    if( error_token_set ){
-      error_token_set.each { error ->
-        println("run_build_scripts_f::all_DAG_DF:: ${error}")
-      }
-   }
-
-  }
-
-
-}
-
-
-/*
- def clean(nodes_to_clean) {
-  def all_dependencies = this.node_map["all"].neighbor.clone()
-  nodes_to_clean.each { node ->
-    all_dependencies.remove(node)
-  }
-
-  def must_have_nodes = []
-  all_dependencies.each { node ->
-    def node_info = this.node_map[node]
-    if (node_info.must_have) {
-      must_have_nodes += node_info.must_have
-    }
-  }
-
-  def to_clean_list = []
-  nodes_to_clean.each { node ->
-    if (!must_have_nodes.contains(node) && this.node_map[node].type == "path") {
-      to_clean_list += node
-    }
-  }
-
-  to_clean_list.each { node ->
-    def file_path = this.node_map[node].label
-    def file = new File(file_path)
-    if (file.exists()) {
-      file.delete()
-      println "Deleted file: ${file_path}"
-    }
-  }
-}
-*/
diff --git a/developer/groovyc/BuildGraph.groovy b/developer/groovyc/BuildGraph.groovy
deleted file mode 100644 (file)
index c68fa43..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-class BuildGraph {
-
-  // Function to load the graph class dynamically
-  static def include_a_class(String a_class_fp) {
-    def class_loader = BuildGraph.class.classLoader
-    def class_name = a_class_fp.replace('/', '.').replace('.class', '')
-    try {
-      return class_loader.loadClass(class_name)
-    } catch (Exception e) {
-      println "Error loading class '${class_name}': ${e.message}"
-      return null
-    }
-  }
-
-  // Build function
-  static def build(String graph_definition_fp, List<String> root_node_labels) {
-
-    // Print summary of what we are doing
-    println "build:: Building targets for graph '${graph_definition_fp}.class'"
-    if (root_node_labels.isEmpty()) {
-      println "No build targets specified. Please provide root node labels to build."
-      System.exit(0)
-    }
-    println "Building targets: ${root_node_labels.join(', ')}"
-
-    // Load the dependency graph class from arg[1]
-    def graph_definition_class = include_a_class(graph_definition_fp)
-    if (graph_definition_class) {
-      println "build:: loaded ${graph_definition_fp}.class"
-    } else {
-      println "build:: failed to load ${graph_definition_fp}.class"
-      System.exit(1)
-    }
-
-    // Get the node_map and node_f_list from the graph class
-    def node_map = graph_definition_class.get_node_map()
-    def node_f_list = graph_definition_class.get_node_f_list()
-    println "node_map: ${node_map}"
-    println "node_f_list: ${node_f_list}"
-
-    // Create an instance of AriadneGraph, and run the build scripts
-    def graph = new AriadneGraph(node_map, node_f_list)
-    graph.run_build_scripts_f(root_node_labels)
-  }
-
-  // Entry point when run as a script
-  static void main(String[] args) {
-    if (args.length == 0) {
-      println "Usage: ./build <graph_definition.class> [root_node_labels...]"
-      System.exit(1)
-    }
-
-    // Get graph definition file and root node labels
-    def graph_definition_fp = args[0]
-    def root_node_labels = args.length > 1 ? args[1..-1] : []
-    build(graph_definition_fp, root_node_labels)
-  }
-}
diff --git a/developer/javac/AriadneGraph.java b/developer/javac/AriadneGraph.java
new file mode 100644 (file)
index 0000000..9b743c8
--- /dev/null
@@ -0,0 +1,684 @@
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+
+public class AriadneGraph {
+
+  /*--------------------------------------------------------------------------------
+   type aliases
+  */
+
+  public interface Token extends String{};
+  public interface TokenSet extends Set<Token>{};
+  public interface Label extends String{};
+  public interface LabelList extends List<Label> {};
+  public interface Node extends Map<Label, Object>{};
+  public interface NodeList extends List<Node>{};
+
+  /*--------------------------------------------------------------------------------
+   instance data 
+  */
+
+  private static Boolean debug = true;
+  private Map<Label, Node> node_map;
+  private List<Function<Label, Node>>> node_f_list;
+
+
+  /*--------------------------------------------------------------------------------
+   constructors
+  */
+
+  /*--------------------------------------------------------------------------------
+    constructors
+  */
+
+  public AriadneGraph(Map<Label, Node> node_map, List<Function<Label, Node>> node_f_list) {
+    if (node_map == null && node_f_list == null) {
+      System.err.println("AriadneGraph: requires one or both of 'node_map' as Map, and 'node_f_list' as List.");
+      System.exit(1);
+    }
+
+    // Initialize node_map and node_f_list to empty collections if they are null
+    this.node_map = (node_map != null) ? node_map : new HashMap<Label, Node>();
+    this.node_f_list = (node_f_list != null) ? node_f_list : new ArrayList<Function<Label, Node>>();
+  }
+
+  /*--------------------------------------------------------------------------------
+   file utilities
+  */
+
+  public static Map<String ,String> unpack_file_path(String file_fp){
+    if(debug) System.out.println("unpack_file_path::file_fp: " + file_fp);
+
+    File file = new File(file_fp);
+    String parent_dp = (file.getParent() != null) ? file.getParent() : "";
+
+    if( !parent_dp.isEmpty() && !parent_dp.endsWith(File.separator) ){
+      parent_dp += File.separator;
+    }
+
+    String file_fn = file.getName();
+    String file_fn_base = file_fn;
+    String file_fn_ext = "";
+
+    int last_index = file_fn.lastIndexOf('.');
+    if(last_index > 0){
+      file_fn_base = file_fn.substring(0 ,last_index);
+      if( last_index + 1 < file_fn.length() ){
+        file_fn_ext = file_fn.substring(last_index + 1);
+      }
+    }
+
+    Map<String ,String> ret_val = new HashMap<>();
+    ret_val.put("dp" ,parent_dp);
+    ret_val.put("fn" ,file_fn);
+    ret_val.put("fn_base" ,file_fn_base);
+    ret_val.put("fn_ext" ,file_fn_ext);
+
+    if(debug) System.out.println("unpack_file_path::ret_val: " + ret_val);
+
+    return ret_val;
+  }
+
+  public static boolean file_exists_q(Label node_label){
+    Path node_path = Paths.get(node_label);
+    return Files.exists(node_path);
+  }
+
+  /*--------------------------------------------------------------------------------
+    About nodes
+
+    A leaf type node specifies a path to a file that should not be deleted by
+    in clean operations. Typically this is the source code. We could add a
+    tool to lock permissions on these before a build, so that the build
+    scripts will also not mess with them (unless they change permissions).
+
+    If the user has multiple dependency graphs defined, a node with no
+    dependencies in one graph, might have dependencies in another.
+
+    An error type node, is one that was found to not have a type, or
+    was constructed by the tool to be a place older, perhaps for a
+    node label that was not found.
+
+  */
+  public static TokenSet all_node_type_set = new HashSet<>(Arrays.asList(
+    "symbol"  
+    ,"path"   
+    ,"leaf"   
+    ,"error"  
+  ));
+
+  public static TokenSet persistent_node_mark_set = new HashSet<>(Arrays.asList(
+    "cycle_member"
+    ,"wellformed"
+    ,"build_failed"
+    ,"null_node"
+  ));
+
+  public static boolean leaf_q(Node node){
+    return node != null && "leaf".equals(node.get("type"));
+  }
+
+  public static boolean has_mark(Node node){
+    return node != null && node.get("mark") != null && !( (TokenSet)node.get("mark") ).isEmpty();
+  }
+
+  public static void set_mark(Node node ,Token mark){
+    if( node.get("mark") == null ){
+      node.put("mark" ,new HashTokenSet());
+    }
+    ( (TokenSet)node.get("mark") ).add(mark);
+  }
+
+  public static void clear_mark(Node node ,Token mark){
+    if( node != null && node.get("mark") != null ){
+      ( (TokenSet) node.get("mark") ).remove(mark);
+    }
+  }
+
+  public static boolean marked_good_q(Node node){
+    return node != null && node.get("mark") != null
+      && ( (TokenSet)node.get("mark") ).contains("wellformed")
+      && !( (TokenSet)node.get("mark") ).contains("cycle_member")
+      && !( (TokenSet)node.get("mark") ).contains("build_failed");
+  }
+
+
+  /*--------------------------------------------------------------------------------
+   Well-formed Node Check
+  */
+
+  public static TokenSet form_condition_set = new HashSet<>(Arrays.asList(
+    "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"
+    ,"neighbor_label_not_in_graph"
+    ,"mark_property_value_must_be_set"
+    ,"unregistered_mark"
+    ,"missing_required_build_code"
+    ,"leaf_given_neighbor_property"
+    ,"leaf_given_build_property"
+  ));
+
+  // given a node, collects a description of its form, returns a set form condition tokens
+  public static TokenSet wellformed_node_q(Node node){
+    TokenSet form_error_set = new HashSet<>();
+
+    if(node == null){
+      form_error_set.add("null_node");
+      return form_error_set;
+    }
+
+    if( !node.containsKey("label") )
+      form_error_set.add("node_must_have_label");
+    else if( !(node.get("label") instanceof Label) )
+      form_error_set.add("label_must_be_string");
+
+    if( !node.containsKey("type") )
+      form_error_set.add("node_must_have_type");
+    else if( !(node.get("type") instanceof String) || !all_node_type_set.contains(node.get("type")) )
+      form_error_set.add("bad_node_type");
+
+    if( node.containsKey("neighbor") ){
+      if( !(node.get("neighbor") instanceof List) )
+        form_error_set.add("neighbor_value_must_be_list");
+      else if( !((List<?>) node.get("neighbor")).stream().allMatch(it -> it instanceof Label) )
+        form_error_set.add("neighbor_reference_must_be_string");
+    }
+
+    if( node.containsKey("mark") ){
+      if( !(node.get("mark") instanceof Set) )
+        form_error_set.add("mark_property_value_must_be_set");
+      else if( !((Set<?>) node.get("mark")).stream().allMatch(it -> persistent_node_mark_set.contains(it)) )
+        form_error_set.add("unregistered_mark");
+    }
+
+    if( "path".equals(node.get("type")) && (!node.containsKey("build") || !(node.get("build") instanceof Runnable)) )
+      form_error_set.add("missing_required_build_code");
+
+    if( "leaf".equals(node.get("type")) ){
+      if( node.containsKey("neighbor") ) form_error_set.add("leaf_given_neighbor_property");
+      if( node.containsKey("build") ) form_error_set.add("leaf_given_build_property");
+    }
+
+    return form_error_set;
+  }
+
+  // given a node, potentially marks it as wellformed, returns one of 'wellformed' or 'malformed'
+  public static Token wellformed_mark_node(Node node ,boolean verbose){
+    if(debug){
+      if(node != null){
+        System.out.println("wellformed_mark_node::node: " + node);
+      }else{
+        System.out.println("wellformed_mark_node given a null node");
+      }
+    }
+
+    TokenSet form_errors = wellformed_node_q(node);
+    if( form_errors.isEmpty() ){
+      set_mark( node ,"wellformed" );
+      return "wellformed";
+    }
+
+    // At this point we know that form_errors is not empty
+    if(verbose){
+      if( node != null && node.get("label") != null && ((Label)node.get("label")).length() > 0 ){
+        System.out.print( "node " + node.get("label") + " is malformed due to:" );
+      }else{
+        System.out.print("anonymous node is malformed due to:");
+      }
+      for(Token error : form_errors){
+        System.out.print(" " + error);
+      }
+      System.out.println("");
+    }
+
+    return "malformed";
+  }
+  public Token wellformed_mark_node(Node node){
+    return wellformed_mark_node(node ,true);
+  }
+
+  // given a node_label, potentially marks the corresponding node as 'wellformed', returns a token set.
+  // Tokens included "undefined_node", "malformed", and "defactor_leaf".
+  public TokenSet wellformed_mark_node_label(Label node_label ,boolean verbose){
+    TokenSet ret_value = new HashSet<>();
+    Node node = lookup(node_label);
+    if(node == null){
+      ret_value.add("undefined_node");
+      return ret_value;
+    }
+    if( "malformed".equals(wellformed_mark_node(node ,verbose)) ){
+      ret_value.add("malformed");
+    }
+    if( ((List<?>)node.get("neighbor")).isEmpty() ){
+      ret_value.add("defacto_leaf"); // might not be `type:leaf`
+    }
+    return ret_value;
+  }
+  public TokenSet wellformed_mark_node_label(Label node_label){
+    return wellformed_mark_node_label(node_label, true)
+  }
+
+
+  /*--------------------------------------------------------------------------------
+   A well formed graph checker.  Traverses entire graph and marks nodes
+   that are not well formed or that are part of a cycle.
+
+   This must be run on the graph for `lookup_marked_good` to work.
+
+   Each node_label must be a string and not empty.
+
+   Subleties here because we have not yet determined if the nodes we are
+   wellformed (after all ,that is what we are determining here).
+
+   If we want to attempt to build 'islands' of things that might be located on
+   the far side of cycles ,then modify the cycle finder to return a list of
+   cycles (i.e. a list of lists) ,then use each of cycle definition (a list) as
+   the root nodes for further search.
+
+   `path_stack` is a stack of LabelList. The first entry is a clone of the list of
+   root nodes, referenced by label. Each subsequent list is a clone of the
+   neighbor list of the leftmost node of the prior entry.
+
+   `path` is a list of the left most nodes, referenced by label, of the entries
+   on the path stack. This is the path to our current location in the tree.
+  */
+
+
+  private boolean find_and_remove_cycle(List<LabelList> path_stack ,LabelList path ,boolean verbose){
+
+    if( path.size() <= 1 ) return false; // 0 or 1 length path can't have a cycle
+
+    // we want to know if the most recent node added to the path occurs at a point earlier
+    // in the path.
+    int rightmost_index = path.size() - 1;
+    Label recent_node_label = path.get( rightmost_index );
+    int cycle_start_index = path.indexOf(recent_node_label);
+    if( cycle_start_index == -1 ){
+      System.err.println("find_and_remove_cycle:: indexOf does not find index of known list member");
+      return false;
+    }
+    Boolean has_cycle =  cycle_start_index < rightmost_index;
+    if(!has_cycle) return false;
+
+    if(verbose) System.out.print("mark_form_graph_descend:: dependency cycle found:");
+    for( Label cycle_node_label : path.subList(cycle_start_index ,path.size()) ){
+      if(verbose) System.out.print(" " + cycle_node_label);
+      Node cycle_node = lookup(cycle_node_label);
+      if( cycle_node.get("mark") == null ){
+        cycle_node.put( "mark" ,new HashTokenSet() );
+      }
+      ( (TokenSet)cycle_node.get("mark") ).add("cycle_member");
+    }
+    if(verbose) System.out.println("");
+
+    // We cannot continue searching after the loop, so we pop back to treat
+    // the first node in the loop as though a leaf node.
+    path_stack.subList( cycle_start_index + 1 ,path_stack.size() ).clear();
+
+    return true;
+  }
+
+  private static TokenSet mark_form_graph_descend_set = new HashSet<>(Arrays.asList(
+    "empty_path_stack"
+    ,"cycle_found"
+    ,"undefined_node"
+    ,"exists_malformed"
+    ,"defacto_leaf"
+  ));
+
+  private TokenSet mark_form_graph_descend( List<LabelList> path_stack ,boolean verbose ){
+    TokenSet ret_value = new HashSet<>();
+    if(path_stack.isEmpty()){
+      if(verbose) System.out.println( "mark_form_graph_descend:: given empty path_stack to descend from" );
+      ret_value.add( "empty_path_stack" );
+      return ret_value;
+    }
+
+    LabelList local_path = new ArrayList<>();
+    for(LabelList path : path_stack){
+      local_path.add( path.get(0) );
+    }
+    Label local_node_label = local_path.get( local_path.size() - 1 );
+  
+    do{
+      
+      if( find_and_remove_cycle(path_stack ,local_path ,verbose) ){
+        ret_value.add("cycle_found");
+        return ret_value;
+      }
+
+      TokenSet wellformed_mark_node_label_result = wellformed_mark_node_label(local_node_label ,verbose);
+      ret_value.addAll( wellformed_mark_node_label_result );
+      if( 
+         wellformed_mark_node_label_result.contains("undefined_node") 
+         || wellformed_mark_node_label_result.contains("defacto_leaf") 
+      ){
+        return ret_value;
+      }
+
+      // Descend further into the tree.
+      path_stack.add( new ArrayList<>((LabelList) lookup(local_node_label).get("neighbor")) );
+      local_node_label = (LabelList)lookup(local_node_label).get("neighbor").get(0);
+      local_path.add(local_node_label);
+
+    }while(true);
+  }
+
+   
+  /*
+    Given root_node_label_list, marks up the graph and returns a set possibly
+    containing 'all_wellformed' and 'cycles_exist'.
+
+    Marks potentially added to each node include 'cycle_member' and 'wellformed'.
+    Note that these marks are independent.
+  */
+  public TokenSet mark_form_graph(LabelList root_node_label_list, boolean verbose){
+    TokenSet ret_value = new HashSet<>();
+    boolean exists_malformed = false;
+    TokenSet result; // used variously
+
+    if( root_node_label_list.isEmpty() ) return ret_value;
+
+    // Initialize the DFS tree iterator.
+    List<LabelList> path_stack = new ArrayList<>();
+    path_stack.add( new ArrayList<>(root_node_label_list) );
+
+    // 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{
+      result = mark_form_graph_descend(path_stack, verbose);
+      if( result.contains("cycle_found") ) ret_value.add("cycle_exists");
+      if( result.contains("undefined_node") ) exists_malformed = true;
+      if( result.contains("exists_malformed") ) exists_malformed = true;
+
+      // increment the iterator to the next leftmost path
+      LabelList top_list = path_stack.get( path_stack.size() - 1 );
+      top_list.remove(0);
+      if( top_list.isEmpty() ) path_stack.remove( path_stack.size() - 1 );
+
+    }while( !path_stack.isEmpty() );
+
+    if( !exists_malformed ) ret_value.add("all_wellformed");
+
+    if( verbose ){
+      if( exists_malformed ) System.out.println("one or more malformed nodes were found");
+      boolean exists_cycle = ret_value.contains("cycle_exists");
+      if( exists_cycle ) System.out.println("one or more cyclic dependency loop found");
+      if( exists_malformed || exists_cycle ) System.out.println("will attempt to build unaffected nodes");
+    }
+
+    return ret_value;
+  }
+  public TokenSet mark_form_graph(LabelList root_node_label_list){
+    return mark_form_graph(root_node_label_list, true);
+  }
+
+
+  /*--------------------------------------------------------------------------------
+    Graph traversal
+  */
+
+  // Given a node label, looks it up in the dependency graph, returns the node or null
+  public Node lookup(Label node_label, boolean verbose){
+    if(node_label == null || node_label.isEmpty()){
+      if(verbose) System.out.println("lookup:: given node_label is null or an empty string");
+      return null;
+    }
+
+    // Try the map
+    Node node = this.node_map.get(node_label);
+    if(node != null){
+      node.put("label", node_label);
+      if(verbose) System.out.println("lookup:: found from map: " + node);
+      return node;
+    }
+    // At this point, node will be null
+
+    // The map lookup failed, let's try the function recognizer list
+    Node match_result = null;
+    for (Function<Label, Node> func : this.node_f_list) {
+      Node match_result = func.apply(node_label); 
+      if("matched".equals(match_result.get("status"))){
+        node = match_result;
+        break;
+      }
+    }
+
+    if(verbose){
+      if(node != null) System.out.println("lookup:: found from recognizer function: " + node);
+      else System.out.println("lookup:: failed to find label: " + node_label);
+    }
+
+    return node;
+  }
+  public Node lookup(Label node_label){
+    return lookup(node_label, true);
+  }
+
+  // Mark aware lookup function
+  public Node lookup_marked_good(Label node_label, boolean verbose){
+    Node node = lookup(node_label, verbose);
+    if(node != null && marked_good_q(node)) return node;
+    return null;
+  }
+  public Node lookup_marked_good(Label node_label){
+    return lookup_marked_good(node_label, true);
+  }
+
+  /*
+    Given `root_node_label_list` of a DAG, applies `node_function` to each node in a
+    depth-first traversal order. Returns a set of error tokens encountered
+    during traversal.
+
+    `mark_form_graph` must be run on the DAG before this function is called, or
+    `lookup_marked_good` will not function correctly.
+  */
+  public TokenSet all_DAG_DF(LabelList root_node_label_list, BiConsumer<Node ,TokenSet> node_function, boolean verbose) {
+    if(verbose) System.out.println("all_DAG_DF::");
+
+    TokenSet error_token_set = new HashSet<>();
+
+    boolean accept_arg_list = true;
+    if(node_function == null) {
+      error_token_set.add("null_node_function");
+      accept_arg_list = false;
+    }
+    if(!(node_function instanceof BiFunction)) {
+      error_token_set.add("node_function_not_a_function");
+      accept_arg_list = false;
+    }
+    if(root_node_label_list == null) {
+      error_token_set.add("null_root_node_label_list");
+      accept_arg_list = false;
+    }
+    if(root_node_label_list.isEmpty()) {
+      error_token_set.add("empty_root_node_label_list");
+      accept_arg_list = false;
+    }
+    if(!accept_arg_list) return error_token_set;
+
+    TokenSet visited = new HashSet<>();
+    List<Node> in_traversal_order = new ArrayList<>();
+
+    Stack<Label> stack = new Stack<>();
+    root_node_label_list.forEach(stack::push);
+
+    while(!stack.isEmpty()) {
+      Label node_label = stack.pop();
+
+      Node node = lookup_marked_good(node_label, verbose);
+      if(node == null) {
+        error_token_set.add("lookup_fail");
+        continue;
+      }
+
+      if(visited.contains(node.get("label"))) continue;
+      visited.add((Label)node.get("label"));
+
+      in_traversal_order.add(node);
+
+      stack.addAll(LabelList)node.get("neighbor"));
+    }
+
+    Collections.reverse(in_traversal_order);
+    for(Node node : in_traversal_order) {
+      node_function.apply(node, error_token_set);
+    }
+
+    return error_token_set;
+  }
+  public TokenSet all_DAG_DF(LabelList root_node_label_list, BiConsumer<Node ,TokenSet> node_function){
+    return all_DAG_DF(root_node_label_list, node_function, true);
+  }
+
+  /*--------------------------------------------------------------------------------
+    run the build scripts
+    depends upon is_acyclic having already marked up the graph.
+  */
+
+  // A dependency is "good" if it is marked good, and for leaf or path, if the
+  // corresponding file exists
+  public boolean good_dependency_q(LabelList node_labels){
+    return node_labels.stream().allMatch(node_label -> {
+        Node node = lookup_marked_good(node_label);
+        if( node == null ) return false;
+        if(
+          ("path".equals(node.get("type")) || "leaf".equals(node.get("type")) )
+          && !file_exists_q( (Label) node.get("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.
+  */
+  public boolean newer_than_all(Label node_label, LabelList node_label_list) throws IOException {
+    Path node_path = Paths.get(node_label);
+    if (!Files.exists(node_path)) return false;
+
+    long node_last_modified = Files.getLastModifiedTime(node_path).toMillis();
+
+    return node_label_list.stream().allMatch(label -> {
+        try {
+          Path path = Paths.get(label);
+          if (!Files.exists(path)) return false;
+          long last_modified = Files.getLastModifiedTime(path).toMillis();
+          return node_last_modified > last_modified;
+        } catch (IOException e) {
+          return false;
+        }
+      });
+  }
+
+  public boolean can_be_built_q(Node node) {
+    if( !marked_good_q(node) ) return false;
+    if(
+       ( "symbol".equals(node.get("type")) || "path".equals(node.get("type")) )
+        && !good_dependency_q( (LabelList)node.get("neighbor") ) 
+    ){
+      return false;
+    }
+    if( 
+       "leaf".equals( node.get("type") ) 
+       && !file_exists_q( (Label)node.get("label") )
+    ){
+      return false;
+    }
+    return true;
+  }
+
+  // `can_be_build_q` must be true for this to be meaningful:
+  public boolean should_be_built_q(Node node, boolean verbose) throws IOException {
+    if ("leaf".equals(node.get("type"))) return false;
+    if ("symbol".equals(node.get("type"))) return true;
+    if ("path".equals(node.get("type"))) return !newer_than_all((Label) node.get("label"), (NodeLabelList) node.get("neighbor"));
+    
+    if (verbose) {
+      System.out.println("should_be_build_q:: unrecognized node type, so assuming it should not be built.");
+    }
+    return false;
+  }
+  public boolean should_be_built_q(Node node) throws IOException {
+    return should_be_built_q(node, true);
+  }
+
+  /*
+    Runs the build scripts, assuming the graph has been marked up already.
+  */
+  public void run_build_scripts_f(NodeLabelList root_node_label_list, boolean verbose) throws IOException {
+
+    if(root_node_label_list.isEmpty()) return;
+
+    TokenSet error_token_set = new HashSet<>(); // used to catch return values
+
+    System.out.println("run_build_script:: Checking if graph is well formed.");
+    error_token_set = mark_form_graph(root_node_label_list);
+    if(error_token_set != null && !error_token_set.isEmpty()) {
+      System.out.println("Graph is not well-formed. Expect build problems. Errors:");
+      error_token_set.forEach(token -> System.out.println("  - " + token));
+    } else {
+      System.out.println("Graph is well-formed. Proceeding with build.");
+    }
+
+    // Define the node function
+    BiConsumer<Node, TokenSet> node_function = (node, error_token_set_2) -> {
+      if(!can_be_built_q(node)) {
+        System.out.println("run_build_scripts_f:: Skipping build for " + node.get("label") + " due to problems with dependencies.");
+        return;
+      }
+      if(!should_be_built_q(node)) {
+        if(verbose) System.out.println("run_build_scripts_f:: " + node.get("label") + " already up to date");
+        return;
+      }
+
+      // Build the target
+      System.out.println("run_build_scripts_f:: Running build script for " + node.get("label"));
+      // Assuming node.build() is a method in the Map or a related object
+      // Replace this with the actual build function for the node
+      // node.build();
+
+      // For path nodes, check if the build updated the target path
+      if("path".equals(node.get("type")) && should_be_built_q(node)) {
+        System.out.println("run_build_scripts_f:: Build failed for " + node.get("label"));
+        set_mark(node, "build_failed");
+      }
+    };
+
+    System.out.println("run_build_scripts_f:: running ...");
+    error_token_set = all_DAG_DF(root_node_label_list, node_function, verbose);
+    if(error_token_set != null) {
+      error_token_set.forEach(error -> System.out.println("run_build_scripts_f::all_DAG_DF:: " + error));
+    }
+  }
+  public void run_build_scripts_f(NodeLabelList root_node_label_list) throws IOException {
+    run_build_scripts_f(root_node_label_list, true);
+  }
+
+}
diff --git a/developer/jvm/Ariadne.jar b/developer/jvm/Ariadne.jar
deleted file mode 100644 (file)
index 42313a5..0000000
Binary files a/developer/jvm/Ariadne.jar and /dev/null differ
index 5024152..f1f3913 100644 (file)
@@ -36,7 +36,87 @@ class TestGraph {
       }
     ]
   }
+  /*
+   import java.util.HashMap;
+   import java.util.Map;
+   import java.io.IOException;
 
+   public class AriadneGraph {
+
+    public static Map<String, Object> java_to_class(String node_label) {
+        System.out.println("java_to_class::");
+
+        Map<String, String> target = AriadneGraph.unpack_file_path(node_label);
+        System.out.println("java_to_class_f:: given target: " + target);
+
+        // This function recognizes <x>.class files
+        if (target.get("fn") == null || !target.get("fn_ext").equals("class")) {
+            Map<String, Object> noMatch = new HashMap<>();
+            noMatch.put("status", "no_match");
+            return noMatch;
+        }
+
+        System.out.println("java_to_class_f:: node_label " + node_label + " matched");
+
+        String class_fp = node_label;
+        String java_fp = target.get("dp") + target.get("fn_base") + ".java";
+
+        // Create the node to return
+        Map<String, Object> matchedNode = new HashMap<>();
+        matchedNode.put("status", "matched");
+        matchedNode.put("label", class_fp);
+        matchedNode.put("type", "path");
+
+        // List of neighbors
+        matchedNode.put("neighbor", List.of(java_fp)); // The corresponding .java file
+
+        // Define the build function as a lambda
+        matchedNode.put("build", (Runnable) () -> {
+            try {
+                Process process = Runtime.getRuntime().exec("javac " + java_fp);
+                process.waitFor();
+
+                if (process.exitValue() == 0) {
+                    Map<String, Object> result = new HashMap<>();
+                    result.put("status", "success");
+                    result.put("output", class_fp);
+                    return result;
+                } else {
+                    Map<String, Object> result = new HashMap<>();
+                    result.put("status", "failure");
+                    result.put("error", new String(process.getErrorStream().readAllBytes()));
+                    return result;
+                }
+            } catch (IOException | InterruptedException e) {
+                Map<String, Object> result = new HashMap<>();
+                result.put("status", "failure");
+                result.put("error", e.getMessage());
+                return result;
+            }
+        });
+
+        return matchedNode;
+    }
+
+    public static Map<String, String> unpack_file_path(String node_label) {
+        // Stub implementation to mimic the unpack_file_path method
+        // This should return a Map containing keys like "fn", "fn_ext", "dp", and "fn_base"
+        Map<String, String> filePathMap = new HashMap<>();
+        filePathMap.put("fn", "ExampleFile");
+        filePathMap.put("fn_ext", "class");
+        filePathMap.put("dp", "/path/to/");
+        filePathMap.put("fn_base", "ExampleFileBase");
+
+        return filePathMap;
+    }
+
+    public static void main(String[] args) {
+        // Example usage
+        Map<String, Object> node = java_to_class("ExampleFile.class");
+        System.out.println(node);
+    }
+}
+*/
   static java_leaf( node_label ){
     println("java_to_leaf::")