From: Thomas Walker Lynch Date: Wed, 2 Oct 2024 15:21:13 +0000 (+0000) Subject: files copied over, starting to test X-Git-Url: https://git.reasoningtechnology.com/home/twl/Documents/subuser/test_env.py?a=commitdiff_plain;h=47801e144d4a0e145a9c65d02c0c6f6e32569abc;p=Ariadne files copied over, starting to test --- diff --git a/developer/deprecated/.gitignore b/developer/deprecated/.gitignore new file mode 100644 index 0000000..120f485 --- /dev/null +++ b/developer/deprecated/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/developer/executor/env_build b/developer/executor/env_build new file mode 100644 index 0000000..2aba1ec --- /dev/null +++ b/developer/executor/env_build @@ -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 index 0000000..2511d79 --- /dev/null +++ b/developer/executor/release @@ -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 index 0000000..6956705 --- /dev/null +++ b/developer/groovy/Ariadne.groovy @@ -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 index 0000000..14c8bce --- /dev/null +++ b/developer/groovy/build @@ -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 " + 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 index 0000000..120f485 --- /dev/null +++ b/developer/temporary/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/developers/Somnus/.gitignore b/developers/Somnus/.gitignore deleted file mode 100644 index 120f485..0000000 --- a/developers/Somnus/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!/.gitignore diff --git a/developers/executor/.githolder b/developers/executor/.githolder deleted file mode 100644 index e69de29..0000000 diff --git a/developers/tanuki/.gitignore b/developers/tanuki/.gitignore deleted file mode 100644 index 120f485..0000000 --- a/developers/tanuki/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!/.gitignore diff --git a/document/directory_naming.txt b/document/directory_naming.txt new file mode 100644 index 0000000..aca6100 --- /dev/null +++ b/document/directory_naming.txt @@ -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 index e69de29..0000000 diff --git a/executor/env_base b/executor/env_base new file mode 100644 index 0000000..1e3b12e --- /dev/null +++ b/executor/env_base @@ -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 index 0000000..0bba479 --- /dev/null +++ b/executor/env_dev @@ -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 index 0000000..6efd4fc --- /dev/null +++ b/executor/env_pm @@ -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 index 0000000..809f8e3 --- /dev/null +++ b/executor/env_tester @@ -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 index 2de57e2..0000000 --- a/readers/directory_naming.txt +++ /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 index 120f485..0000000 --- a/tanuki/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!/.gitignore diff --git a/temporary/.gitignore b/temporary/.gitignore new file mode 100644 index 0000000..120f485 --- /dev/null +++ b/temporary/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/tester/.githolder b/tester/.githolder new file mode 100644 index 0000000..e69de29 diff --git a/tester/executor/env_test b/tester/executor/env_test new file mode 100644 index 0000000..d248665 --- /dev/null +++ b/tester/executor/env_test @@ -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 index 0000000..f7c8d83 --- /dev/null +++ b/tester/test0/env_test0 @@ -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 index 0000000..a937617 --- /dev/null +++ b/tester/test0/graph.groovy @@ -0,0 +1,2 @@ +def node_map = [:] +def node_f_list = [] diff --git a/tool/.gitignore b/tool/.gitignore new file mode 100644 index 0000000..de7fe7a --- /dev/null +++ b/tool/.gitignore @@ -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 index 0000000..120f485 --- /dev/null +++ b/tool/upstream/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/toolsmiths/.githolder b/toolsmiths/.githolder deleted file mode 100644 index e69de29..0000000 diff --git a/user/.githolder b/user/.githolder new file mode 100644 index 0000000..e69de29 diff --git a/user/Ariadne_0.1/Ariadne.groovy b/user/Ariadne_0.1/Ariadne.groovy new file mode 100644 index 0000000..6956705 --- /dev/null +++ b/user/Ariadne_0.1/Ariadne.groovy @@ -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 index 0000000..14c8bce --- /dev/null +++ b/user/Ariadne_0.1/build @@ -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 " + System.exit(1) +} + +def graph_fp = args[0] +build(graph_fp)