--- /dev/null
+*
+!/.gitignore
--- /dev/null
+#!/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"
--- /dev/null
+#!/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"
--- /dev/null
+/*
+ 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
--- /dev/null
+#!/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)
--- /dev/null
+*
+!/.gitignore
+++ /dev/null
-*
-!/.gitignore
+++ /dev/null
-*
-!/.gitignore
--- /dev/null
+
+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
--- /dev/null
+#!/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"
--- /dev/null
+#!/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"
--- /dev/null
+#!/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"
--- /dev/null
+#!/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"
+++ /dev/null
-
-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.
+++ /dev/null
-*
-!/.gitignore
--- /dev/null
+*
+!/.gitignore
--- /dev/null
+#!/usr/bin/env bash
+# environment common to all tests
+# each test directory also has an environment
\ No newline at end of file
--- /dev/null
+
+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"
--- /dev/null
+def node_map = [:]
+def node_f_list = []
--- /dev/null
+*
+!/.gitignore
+!/document
+# upstream has a .gitignore file in it
+!/upstream
--- /dev/null
+*
+!/.gitignore
--- /dev/null
+/*
+ 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
--- /dev/null
+#!/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)