files copied over, starting to test
authorThomas Walker Lynch <xtujpz@reasoningtechnology.com>
Wed, 2 Oct 2024 15:21:13 +0000 (15:21 +0000)
committerThomas Walker Lynch <xtujpz@reasoningtechnology.com>
Wed, 2 Oct 2024 15:21:13 +0000 (15:21 +0000)
28 files changed:
developer/deprecated/.gitignore [new file with mode: 0644]
developer/executor/env_build [new file with mode: 0644]
developer/executor/release [new file with mode: 0755]
developer/groovy/Ariadne.groovy [new file with mode: 0644]
developer/groovy/build [new file with mode: 0755]
developer/temporary/.gitignore [new file with mode: 0644]
developers/Somnus/.gitignore [deleted file]
developers/executor/.githolder [deleted file]
developers/tanuki/.gitignore [deleted file]
document/directory_naming.txt [new file with mode: 0644]
executor/.githolder [deleted file]
executor/env_base [new file with mode: 0644]
executor/env_dev [new file with mode: 0644]
executor/env_pm [new file with mode: 0644]
executor/env_tester [new file with mode: 0644]
readers/directory_naming.txt [deleted file]
tanuki/.gitignore [deleted file]
temporary/.gitignore [new file with mode: 0644]
tester/.githolder [new file with mode: 0644]
tester/executor/env_test [new file with mode: 0644]
tester/test0/env_test0 [new file with mode: 0644]
tester/test0/graph.groovy [new file with mode: 0644]
tool/.gitignore [new file with mode: 0644]
tool/upstream/.gitignore [new file with mode: 0644]
toolsmiths/.githolder [deleted file]
user/.githolder [new file with mode: 0644]
user/Ariadne_0.1/Ariadne.groovy [new file with mode: 0644]
user/Ariadne_0.1/build [new file with mode: 0755]

diff --git a/developer/deprecated/.gitignore b/developer/deprecated/.gitignore
new file mode 100644 (file)
index 0000000..120f485
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!/.gitignore
diff --git a/developer/executor/env_build b/developer/executor/env_build
new file mode 100644 (file)
index 0000000..2aba1ec
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+# The build environment. 
+#
+
+# Ensure the script is sourced rather than run directly
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+  echo "This script must be sourced, not executed. Exiting."
+  return 1
+fi
+
+export ENV_BUILD_VERSION="0.1"
diff --git a/developer/executor/release b/developer/executor/release
new file mode 100755 (executable)
index 0000000..2511d79
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env groovy
+
+// Access the environment variable REPO_HOME
+def repo_home = System.getenv('REPO_HOME')
+if (!repo_home) {
+    println "Error: REPO_HOME is not set."
+    System.exit(1)
+}
+
+def version = "0.1"
+def release_dir = "${repo_home}/user/Ariadne_${version}"
+def build_fp = "${repo_home}/developer/groovy/build"
+def ariadne_lib_fp = "${repo_home}/developer/groovy/Ariadne.groovy"
+
+// Check if the release directory exists, if not, create it
+def release_dir_file = new File(release_dir)
+if (!release_dir_file.exists()) {
+    release_dir_file.mkdirs()
+}
+
+// Function to use 'install' command for copying and setting permissions
+def install_file(source_fp, target_dp, perms) {
+    def target_file = "${target_dp}/${new File(source_fp).name}"
+    def cmd = ["install", "-m", perms, source_fp, target_file]
+    def process = cmd.execute()
+    process.waitFor()
+
+    if (process.exitValue() != 0) {
+        println "Error: Failed to install ${new File(source_fp).name} to ${target_dp}"
+        println process.err.text
+        System.exit(1)
+    }
+
+    println "Installed ${new File(source_fp).name} to ${target_dp} with permissions ${perms}"
+}
+
+// Install the Shell UI and Ariadne.groovy into the release directory with the correct permissions
+install_file(build_fp, release_dir, "ug+r,ug+x")    // Executable script
+install_file(ariadne_lib_fp, release_dir, "ug+r")   // Readable script
+
+println "Release version ${version} completed at $release_dir"
diff --git a/developer/groovy/Ariadne.groovy b/developer/groovy/Ariadne.groovy
new file mode 100644 (file)
index 0000000..6956705
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ 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_fn = 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')
+    }
+
+  }
+
+  // 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/groovy/build b/developer/groovy/build
new file mode 100755 (executable)
index 0000000..14c8bce
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/env groovy
+
+def include_ariadne_library(){
+
+  // Get the directory where this script is located
+  def script_dp = new File(getClass().protectionDomain.codeSource.location.path).parent
+  def ariadne_lib_fp = new File(script_dp ,"Ariadne.groovy")
+
+  if(!ariadne_lib_fp.exists()){
+    println "Error: Ariadne library not found in ${script_dp}"
+    System.exit(1)
+  }
+
+  return ariadne_lib_fp.text
+}
+
+
+// Main build function for the Shell UI
+def build(graph_fp){
+  // Check if the graph definition exists
+  def graph_fn = new File(graph_fp)
+  if(!graph_fn.exists()){
+    println "Error: Graph definition file not found: $graph_fp"
+    System.exit(1)
+  }
+
+  // Prepare the binding and shell for evaluation
+  def binding = new Binding()
+  def shell = new GroovyShell(binding)
+
+  // Load the graph definition into the shell
+  shell.evaluate(graph_fn.text)
+
+  // Check if node_map and node_f_list are defined as local variables
+  if (!binding.variables.containsKey('node_map') || !binding.variables.containsKey('node_f_list')) {
+    println "Error: Graph definition must define both 'node_map' and 'node_f_list'"
+    System.exit(1)
+  }
+
+  // Load the Ariadne library functions
+  shell.evaluate(include_ariadne_library())
+
+  // Call the build function in Ariadne.groovy
+  run_build_scripts(binding.getVariable('node_map'), binding.getVariable('node_f_list'))
+}
+
+// Entry point for the script
+if(args.length == 0){
+  println "Usage: ./shellUI.groovy <graph_definition>"
+  System.exit(1)
+}
+
+def graph_fp = args[0]
+build(graph_fp)
diff --git a/developer/temporary/.gitignore b/developer/temporary/.gitignore
new file mode 100644 (file)
index 0000000..120f485
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!/.gitignore
diff --git a/developers/Somnus/.gitignore b/developers/Somnus/.gitignore
deleted file mode 100644 (file)
index 120f485..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!/.gitignore
diff --git a/developers/executor/.githolder b/developers/executor/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/developers/tanuki/.gitignore b/developers/tanuki/.gitignore
deleted file mode 100644 (file)
index 120f485..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!/.gitignore
diff --git a/document/directory_naming.txt b/document/directory_naming.txt
new file mode 100644 (file)
index 0000000..aca6100
--- /dev/null
@@ -0,0 +1,82 @@
+
+Naming Conventions for Directories
+
+  Our shop has a convention of naming directories after the role of the person,
+  the name of the program, or generally, the agent, who is going to use the
+  files in the directory. In short, the name is often the answer to the question
+  "who are the files are for?".
+
+  Sometimes there is not a good answer to that question. A good example is the
+  documents directory. There really isn't a good term for the role that people
+  are playing when the read the documents.  Perhaps, 'readers'? This is not a
+  job function, and it is a somewhat ambiguous.  Perhaps, 'projectologist'? Ah
+  nah ..
+
+  When we can not answer the question of who the files are for, we instead
+  choose another common property shared by each and every file in the directory.
+  It is often the case that a property that each and every file has in common
+  will be singular.  Hence in the example in the prior paragraph, when each file
+  in a directory has the property of being a document, the directory gets called
+  'document'.
+
+Top Level Directory
+
+  The top level of a github project is of course named after the project. Though
+  we people like to see actors related to project names. Look at all the
+  mythical animals on the covers of the O'Reilley manuals.
+
+  The top level directory of our git project is reserved for project manager to
+  use. The project manager builds the directory structure, initializes the
+  repository, installs tools that will be needed, and generally administers
+  the project.
+
+  In the environment, the top level directory is located at `$REPO_HOME`
+
+Developer
+
+  The developer's directory is located at `$REPO_HOME/developer`.
+
+  This directory contains the developer's workspace.  Developers are free to
+  organize it in any manner they see fit, though they should continue to follow the
+  convention of naming directories after the agents that operate on the contained files.
+
+  As examples,
+
+  - Files for the **C compiler** are placed in the `cc` directory, since they
+    are "for" the C compiler.
+
+  - Files for the **Java compiler** (javac) are stored in the `javac` directory.
+
+  - Similar naming conventions are followed for other tools. For instance, if
+    the project involves files for another tool or compiler, the directory is
+    named after that tool.
+
+Executor
+
+  Sometimes multiple related actors operate on the files in a directory. In
+  which case we give the directory a more general name that describes the
+  actors as a group.
+
+  So if we had a directory that held a mix of files for various compilers we might
+  name the directory 'compiler_input' or even 'compiler'.
+
+  One common generalization is 'executor'. An executor is any program that runs
+  another program; examples include shells that interpret shell scripts;
+  various language interpreter; and most famously the machine loader, which will
+  load an instruction sequence into memory and point the program counter at it.
+
+Temporary
+
+  This is a scratch pad directory used by programs. Files will appear
+  and disappear from here.  There is no reason a developer can not manually
+  add a file, but scripts such as `make clean`, might delete it. Directories
+  with this name should be git ignored.
+
+Deprecated
+
+  As a developer I often have files that I set aside just in case I want to look
+  at them again. Sometimes I plan to bring them back later. Unlike temporary files,
+  they are not deleted by any clean script or any program that is using them as
+  intermediate files. This directory is also git ignored.
+
+LocalWords:  projectologist
diff --git a/executor/.githolder b/executor/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/executor/env_base b/executor/env_base
new file mode 100644 (file)
index 0000000..1e3b12e
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+# Ensure the script is sourced
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+  echo "This script must be sourced, not executed. Exiting."
+  return 1
+fi
+
+# These are things set by the `repo` command found in the `resource` project,
+# but if you don't have that, then source this into the environment.
+
+script_path="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
+export REPO_HOME="${script_path%/*}"
+export PROJECT=$(basename "$REPO_HOME")
+
+PPS1="\n[$PROJECT]\n\u@\h§$(pwd)§\n> "
+PPS2=">> "
+
+echo REPO_HOME "$REPO_HOME"
+echo PROJECT "$PROJECT"
+echo "${BASH_SOURCE[0]}" "complete"
diff --git a/executor/env_dev b/executor/env_dev
new file mode 100644 (file)
index 0000000..0bba479
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+# Ensure the script is sourced
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+  echo "This script must be sourced, not executed. Exiting."
+  return 1
+fi
+
+# Check if REPO_HOME is set, if not source env_base
+if [ -z "$REPO_HOME" ]; then
+  script_path="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
+  source "${script_path}/env_base"
+fi
+
+export JAVA_HOME="$REPO_HOME/tool/jdk-11"
+export GROOVY_HOME="$REPO_HOME/tool/groovy-4.0.9"
+
+export PATH=\
+"$REPO_HOME"/developer/executor\
+:"$JAVA_HOME"/bin\
+:"$GROOVY_HOME"/bin\
+:"$PATH"
+
+# so the .gitignore files can be seen:
+alias ls="ls -a"
+
+# Corrected line:
+cd "$REPO_HOME/developer"
+
+source "$REPO_HOME"/developer/executor/env_build
+
+echo "${BASH_SOURCE[0]}" "complete"
diff --git a/executor/env_pm b/executor/env_pm
new file mode 100644 (file)
index 0000000..6efd4fc
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# Ensure the script is sourced
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+  echo "This script must be sourced, not executed. Exiting."
+  return 1
+fi
+
+# Check if REPO_HOME is set, if not source env_base
+if [ -z "$REPO_HOME" ]; then
+  script_path="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
+  source "${script_path}/env_base"
+fi
+
+PROJECT="$PROJECT"_PM
+
+export PATH=\
+"$REPO_HOME"/executor\
+:"$PATH"
+
+# no sneaky hidden files
+alias ls="ls -a"
+
+echo "${BASH_SOURCE[0]}" "complete"
diff --git a/executor/env_tester b/executor/env_tester
new file mode 100644 (file)
index 0000000..809f8e3
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# Ensure the script is sourced
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+  echo "This script must be sourced, not executed. Exiting."
+  return 1
+fi
+
+# Check if REPO_HOME is set, if not source env_base
+if [ -z "$REPO_HOME" ]; then
+  script_path="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
+  source "${script_path}/env_base"
+fi
+
+PROJECT="$PROJECT"_TESTER
+
+export PATH=\
+"$REPO_HOME"/tester/executor\
+:"$PATH"
+
+cd "$REPO_HOME"/tester
+source executor/env_test
+
+echo "${BASH_SOURCE[0]}" "complete"
diff --git a/readers/directory_naming.txt b/readers/directory_naming.txt
deleted file mode 100644 (file)
index 2de57e2..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-
-Naming Conventions for Directories
-
-  Our shop has a convention of naming directories after the role of the person,
-  the name of the program, or generally, the agent, that is going to use
-  the files in the directory.  In short, "directories are named after the agent the
-  files are for."
-
-  This convention makes it easier for people to make sense of directory names. It
-  is also natural for people to think in terms of the actors doing things. Indeed
-  our ancestors saw the hand of a person, spirit, or God behind everything.
-
-Top Level Directory
-
-  Sometimes this naming convention is not practical.  The top level of a github
-  project must be named after the project. Though note here to the human
-  tendency to see actors. Look at all the mythical animals on the covers of the
-  O'Reilley manuals.
-
-  The top level directory of our git project is reserved for project manager to
-  use. It will have files such as `.git`, and various scripts for maintaining
-  the project. The project manager will install the tools used in a directory
-  called 'toolsmiths.  There is a sub directory for the developers, called
-  'developers'.
-
-  The top level directory is located at `$REPO_HOME`
-
-Developers
-
-  The developer’s directory is located at `$REPO_HOME/developers`, and is
-  known in the environment as `$DEVELOPERS_HOME`. 
-
-  This directory contains the developers' workspace.  Developers are free to
-  organize it in any manner they see fit, though they should continue to follow the
-  convention of naming directories after the agents that operate on the contained files.
-
-  As examples,
-
-  - Files for the **C compiler** are placed in the `cc` directory, since they
-    are "for" the C compiler.
-
-  - Files for the **Java compiler** (javac) are stored in the `javac` directory.
-
-  - Similar naming conventions are followed for other tools. For instance, if
-    the project involves files for another tool or compiler, the directory is
-    named after that tool.
-
-Executor
-
-  Sometimes multiple actors operate on the files in a directory. In which case
-  we give the directory a more general name that describes the actors.
-
-  So if we had a directory that held a mix of files for various compilers we might
-  name the directory 'compiler_input' or even 'compiler'.
-
-  One common generalization is 'executor'. An executor is any program that runs
-  another program; examples include shells that interpret shell scripts;
-  various language interpreter; and most famously the machine loader, which will
-  load an instruction sequence into memory and point the program counter at it.
-
-Readers
-
-  This refers to humans or AIs. It is short for 'Dear Readers,".  These
-  files are for those rare individuals who read documents.
-
-Files for mythological beings
-
-  I've had a bit of fun and introduced a couple of these. We wouldn't want too
-  many.
-
-  tanuki, たぬき
-
-  Both a mythical beast, and a real animal. A kind of raccoon, which like all
-  raccoons, will steal things, especially food. Files left within reach of a
-  tanuki, are likely to disappear without notice. Typically these are things
-  such as intermediate files during a build.
-
-  Somnus
-
-  As a developer I often have files that I set aside just in case I want to look
-  at them again. Sometimes I plan to bring them back later. Unlike tanuki files,
-  they are not deleted by any clean script or any program that is using them as
-  intermediate files. However, they do not appear it the repo. Somnus is the
-  Roman god of sleep.
diff --git a/tanuki/.gitignore b/tanuki/.gitignore
deleted file mode 100644 (file)
index 120f485..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!/.gitignore
diff --git a/temporary/.gitignore b/temporary/.gitignore
new file mode 100644 (file)
index 0000000..120f485
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!/.gitignore
diff --git a/tester/.githolder b/tester/.githolder
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tester/executor/env_test b/tester/executor/env_test
new file mode 100644 (file)
index 0000000..d248665
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# environment common to all tests
+# each test directory also has an environment
\ No newline at end of file
diff --git a/tester/test0/env_test0 b/tester/test0/env_test0
new file mode 100644 (file)
index 0000000..f7c8d83
--- /dev/null
@@ -0,0 +1,9 @@
+
+export JAVA_HOME="$REPO_HOME/tool/jdk-11"
+export GROOVY_HOME="$REPO_HOME/tool/groovy-4.0.9"
+
+export PATH=\
+"$REPO_HOME"/user/Ariadne_0.1\
+:"$JAVA_HOME"/bin\
+:"$GROOVY_HOME"/bin\
+:"$PATH"
diff --git a/tester/test0/graph.groovy b/tester/test0/graph.groovy
new file mode 100644 (file)
index 0000000..a937617
--- /dev/null
@@ -0,0 +1,2 @@
+def node_map = [:]
+def node_f_list = []
diff --git a/tool/.gitignore b/tool/.gitignore
new file mode 100644 (file)
index 0000000..de7fe7a
--- /dev/null
@@ -0,0 +1,5 @@
+*
+!/.gitignore
+!/document
+# upstream has a .gitignore file in it
+!/upstream
diff --git a/tool/upstream/.gitignore b/tool/upstream/.gitignore
new file mode 100644 (file)
index 0000000..120f485
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!/.gitignore
diff --git a/toolsmiths/.githolder b/toolsmiths/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/user/.githolder b/user/.githolder
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/user/Ariadne_0.1/Ariadne.groovy b/user/Ariadne_0.1/Ariadne.groovy
new file mode 100644 (file)
index 0000000..6956705
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ 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_fn = 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')
+    }
+
+  }
+
+  // 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/user/Ariadne_0.1/build b/user/Ariadne_0.1/build
new file mode 100755 (executable)
index 0000000..14c8bce
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/env groovy
+
+def include_ariadne_library(){
+
+  // Get the directory where this script is located
+  def script_dp = new File(getClass().protectionDomain.codeSource.location.path).parent
+  def ariadne_lib_fp = new File(script_dp ,"Ariadne.groovy")
+
+  if(!ariadne_lib_fp.exists()){
+    println "Error: Ariadne library not found in ${script_dp}"
+    System.exit(1)
+  }
+
+  return ariadne_lib_fp.text
+}
+
+
+// Main build function for the Shell UI
+def build(graph_fp){
+  // Check if the graph definition exists
+  def graph_fn = new File(graph_fp)
+  if(!graph_fn.exists()){
+    println "Error: Graph definition file not found: $graph_fp"
+    System.exit(1)
+  }
+
+  // Prepare the binding and shell for evaluation
+  def binding = new Binding()
+  def shell = new GroovyShell(binding)
+
+  // Load the graph definition into the shell
+  shell.evaluate(graph_fn.text)
+
+  // Check if node_map and node_f_list are defined as local variables
+  if (!binding.variables.containsKey('node_map') || !binding.variables.containsKey('node_f_list')) {
+    println "Error: Graph definition must define both 'node_map' and 'node_f_list'"
+    System.exit(1)
+  }
+
+  // Load the Ariadne library functions
+  shell.evaluate(include_ariadne_library())
+
+  // Call the build function in Ariadne.groovy
+  run_build_scripts(binding.getVariable('node_map'), binding.getVariable('node_f_list'))
+}
+
+// Entry point for the script
+if(args.length == 0){
+  println "Usage: ./shellUI.groovy <graph_definition>"
+  System.exit(1)
+}
+
+def graph_fp = args[0]
+build(graph_fp)