#!/bin/bash # If this file has already been sourced, just return test $(ENVIRONMENT_SH+isset) && return declare -g ENVIRONMENT_SH=true # # Environtments are stored as a list of lists # Each list is a "call stack", and popped from the list of lists when the current scope is exited # Each list is of the form # (name-1 variable-token-1 ... name-N variable-token-N) # Where: # name is the name of the variable in the environment # variable-key is the token to lookup a value using the variable:: api # source ${BASH_SOURCE%/*}/common.sh source ${BASH_SOURCE%/*}/variables.sh source ${BASH_SOURCE%/*}/variables.arraylist.sh source ${BASH_SOURCE%/*}/variables.linkedlist.sh source ${BASH_SOURCE%/*}/variables.stack.sh source ${BASH_SOURCE%/*}/variables.queue.sh source ${BASH_SOURCE%/*}/variables.map.sh declare -g ENVIRONMENT_DEBUG=0 # TODO: This needs to use a LinkedList implementation for the base list of scopes # Each scope, on the other hand, should be a Map # # environment::new # # Creates a brand new environment # Generally will only be called once at the setup of the interpreter # # Returns: The variable-token for the newly created environment # proc environment::new { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::new $(@)" ; } variable::LinkedList::new ; setglobal env = $(RESULT) variable::Map::new variable::LinkedList::prepend $(env) $(RESULT) } # # environment::addScope # # Adds a new scope to the to the environment # # Returns: The same environment variable-token that was passed in # proc environment::pushScope { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::pushScope $(@)" ; } declare env="$(1)" variable::Map::new variable::LinkedList::prepend $(env) $(RESULT) setglobal RESULT = $(RESULT) } # # environment::popScope # # Removes the top level environment from the environment list # TODO: Need better names to distinguish "a list of environment lists" and # "a list of name value-token pairs" # # Returns: The same environment variable-token that was passed in # proc environment::popScope { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::popScope $(@)" ; } variable::LinkedList::rest $1 setglobal RESULT = $(RESULT) } # # environment::hasValue # # Returns 0 if the key in question exists in the environment # proc environment::hasValue { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::getValue $(@)" ; } declare env="$(1)" declare name="$(2)" declare scope variable::new String $(name) ; declare keyToken="$(RESULT)" declare currentEnv="$(env)" while ! variable::LinkedList::isEmpty_c $(currentEnv) { variable::LinkedList::first $(currentEnv) setglobal scope = $(RESULT) if variable::Map::containsKey_c $(scope) $(name) { return 0 } variable::LinkedList::rest $(currentEnv) setglobal currentEnv = $RESULT } return 1 } # # environment::getValue # # Gets the value for a given key in the specified env # Error if key does not exist # proc environment::getValue { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::getValue $(@)" ; } declare env="$(1)" declare name="$(2)" declare scope variable::new String $(name) ; declare keyToken="$(RESULT)" declare currentEnv="$(env)" while ! variable::LinkedList::isEmpty_c $(currentEnv) { variable::LinkedList::first $(currentEnv) setglobal scope = $(RESULT) if variable::Map::containsKey_c $(scope) $(name) { variable::Map::get $(scope) $(name) return } variable::LinkedList::rest $(currentEnv) setglobal currentEnv = $RESULT } variable::value $name stderr "Variable [$(name)=$(RESULT)] not found in current environment" exit 1 } # # environment::setVariable # # Sets a name/variable-token pair on the top level of the environment # # Returns 0 if the variable already existed (and was changed) # 1 if the variable is new # # proc environment::setVariable { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::setVariable $(@)" ; } declare env="$(1)" declare keyToken="$(2)" declare valueToken="$(3)" variable::LinkedList::first $(env) declare scope="$(RESULT)" declare returnValue if variable::Map::containsKey_c $(scope) $(keyToken) { setglobal returnValue = '0' } else { setglobal returnValue = '1' } variable::Map::put $(scope) $(keyToken) $(valueToken) setglobal RESULT = ''"" return $returnValue } # # environment::setVariable # # Sets a name/variable-token pair whereever it is found in the environment list # or, if the name in question is not found, adds it to the top level # proc environment::setOrReplaceVariable { if [[ ${ENVIRONMENT_DEBUG} == 1 ]] { stderr "environment::setOrReplaceVariable $(@)" ; } declare env="$(1)" declare keyToken="$(2)" declare valueToken="$(3)" declare scope declare currentEnv="$(env)" while ! variable::LinkedList::isEmpty_c $(currentEnv) { variable::LinkedList::first $(currentEnv) setglobal scope = $(RESULT) if variable::Map::containsKey_c $(scope) $(keyToken) { variable::Map::put $(scope) $(keyToken) $(valueToken) setglobal RESULT = ''"" return 0 } variable::LinkedList::rest $(currentEnv) setglobal currentEnv = $RESULT } # Wasn't found, add it to the first scope in the env variable::LinkedList::first $(env) setglobal scope = $(RESULT) variable::Map::put $(scope) $(keyToken) $(valueToken) setglobal RESULT = ''"" return 1 } proc environment::print { declare currentEnv="$(1)" echo "Environment [$(currentEnv)]" while ! variable::LinkedList::isEmpty_c $(currentEnv) { variable::LinkedList::first $(currentEnv) variable::Map::print $(RESULT) variable::LinkedList::rest $(currentEnv) setglobal currentEnv = $RESULT } } # ====================================================== if test $0 != $BASH_SOURCE { return } variable::new String "key one" ; setglobal key1 = $(RESULT) variable::new String "value one" ; setglobal value1 = $(RESULT) variable::new String "key two" ; setglobal key2 = $(RESULT) variable::new String "value two" ; setglobal value2 = $(RESULT) variable::new String "key three" ; setglobal key3 = $(RESULT) variable::new String "value three" ; setglobal value3 = $(RESULT) variable::new String "no such key" ; setglobal keyUnknown = $(RESULT) declare env1 declare env2 declare env3 environment::new ; setglobal env1 = $(RESULT) environment::setVariable $(env1) $key1 $value1 # Check that we can get the value out environment::getValue $(env1) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value one" $(RESULT) "Single variable" environment::setVariable $(env1) $key2 $value2 # Check that we can get the previously existing key:value out environment::getValue $(env1) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value one" $(RESULT) "Multiple variables, first" # And that we can get the new value out environment::getValue $(env1) $key2 ; \ variable::value $(RESULT) ; \ assert::equals "value two" $(RESULT) "Multiple variables, second" # # Multiple scope tests / set # environment::new ; setglobal env1 = $(RESULT) environment::setVariable $(env1) $key1 $value1 environment::pushScope $(env1) ; setglobal env2 = $(RESULT) environment::setVariable $(env2) $key2 $value2 # Make sure we can get the new value out environment::getValue $(env2) $key2 ; \ variable::value $(RESULT) ; \ assert::equals "value two" $(RESULT) "Second scope" # And a variable from the original scope environment::getValue $(env2) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value one" $(RESULT) "Variable from first scope" # Pop off the second scope environment::popScope $(env2) ; setglobal env3 = $(RESULT) # and make sure a variable from the first scope is still there environment::getValue $(env3) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value one" $(RESULT) "Variable from first scope post pop" # and the variable from the second scope is not environment::hasValue $(env3) $key2 ; \ assert::equals 1 $Status "Variable from second scope after we popped it" # # Multiple scope tests / setOrReplace # environment::new ; setglobal env1 = $(RESULT) environment::setOrReplaceVariable $(env1) $key1 $value1 environment::pushScope $(env1) ; setglobal env2 = $(RESULT) environment::setOrReplaceVariable $(env2) $key2 $value2 environment::setOrReplaceVariable $(env2) $key1 $value3 # Make sure we can get the new value out environment::getValue $(env2) $key2 ; \ variable::value $(RESULT) ; \ assert::equals "value two" $(RESULT) "Second scope" # And a variable from the original scope environment::getValue $(env2) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value three" $(RESULT) "Variable from first scope" # Pop off the second scope environment::popScope $(env2) ; setglobal env3 = $(RESULT) # and make sure a variable from the first scope is still there (new value) environment::getValue $(env3) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value three" $(RESULT) "Variable from first scope post pop" # and the variable from the second scope is not environment::hasValue $(env3) $key2 ; \ assert::equals 1 $Status "Variable from second scope after we popped it" # # Second scope, [set] value of variable, then make sure the original env has the old value # environment::new ; setglobal env1 = $RESULT environment::setVariable $env1 $key1 $value1 environment::pushScope $(env1) ; setglobal env2 = $RESULT environment::setVariable $(env2) $key1 $value2 environment::getValue $(env1) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value one" $(RESULT) "setVariable, original env" environment::getValue $(env2) $key1 ; \ variable::value $(RESULT) ; \ assert::equals "value two" $(RESULT) "setVariable, new env" # # # assert::report if test $(1+isset) && test $1 == "debug" { variable::printMetadata }