#!/bin/bash # If this file has already been sourced, just return test $(VARIABLES_SH+true) && return declare -g VARIABLES_SH=true source ${BASH_SOURCE%/*}/common.sh source ${BASH_SOURCE%/*}/logger.sh # TODO: Only set this if it doesn't already exist # # Functions that end in _p send their result to stdout (accessed via $(subshell execution)) # They cannot modify data, so can only be used in getters # They should only be used for debugging # # Functions that end in _c return their result via [return 0/1] to signify true/false # # All other functions return their results in the global RESULT # # handle=[type] declare -g -A VARIABLES_METADATA=() declare -g -A VARIABLES_VALUES=() declare -g VARIABLES_INDEX=0 declare -g -A VARIABLES_OFFSETS=([type]=0) declare -g VARIABLES_DEBUG=0 declare -g -A VARIABLES_TYPES=() # == GENERAL == proc variable::new { if [[ ${VARIABLES_DEBUG} == 1 ]] { stderr "variable::new $(@)" ; } declare token if [[ "${1}" == "-name" ]] { shift setglobal token = $(1) shift } else { setglobal token = ""auto#$(VARIABLES_INDEX)"" setglobal VARIABLES_INDEX = $shExpr(' ${VARIABLES_INDEX} + 1 ') } if [[ "${#@}" -lt 1 || "${#@}" -gt 2 ]] { stderr "Usage: variable::new ?name? " exit 1 } declare type="$(1)" if test ! $(VARIABLES_TYPES[${type}]+isset) { stderr "Unknown variable type [$(type)]" exit 1 } if [[ "${#@}" -eq 1 ]] { declare value="" } else { declare value="$(2)" } declare -a metadata=($type) compat array-assign VARIABLES_METADATA '"${token}"' $(metadata[@]) compat array-assign VARIABLES_VALUES '"${token}"' $value #echo "Creating a new [$1] of [$2] at index [${index}]" #echo "Result=${index}" setglobal RESULT = $token } proc _variable::new_p { variable::new $(@) echo $RESULT } proc variable::clone { if [[ ${VARIABLES_DEBUG} == 1 ]] { stderr "variable::clone $(@)" ; } if [[ "${1}" == "-name" ]] { shift declare token="$(1)" shift } else { declare token="auto#$(VARIABLES_INDEX)" setglobal VARIABLES_INDEX = $shExpr(' ${VARIABLES_INDEX} + 1 ') } if [[ "${#@}" -lt 1 || "${#@}" -gt 2 ]] { stderr "Usage: variable::new ?name? " exit 1 } declare from_token="$(1)" variable::type $(from_token) ; declare type="$(RESULT)" variable::value $(from_token) ; declare value="$(RESULT)" declare -a metadata=($type) compat array-assign VARIABLES_METADATA '${token}' $(metadata[@]) compat array-assign VARIABLES_VALUES '${token}' $value setglobal RESULT = $token } # # variable::set # proc variable::set { if [[ ${VARIABLES_DEBUG} == 1 ]] { stderr "variable::set $(@)" ; } if [[ ${#@} -ne 3 ]] { stderr "Usage: variable::set " exit 1 } declare token="$1" declare type="$2" declare value="$3" if test ! $(VARIABLES_TYPES[${type}]+isset) { stderr "Unknown variable type [$(type)]" exit 1 } declare -a metadata=($type) compat array-assign VARIABLES_METADATA '${token}' $(metadata[@]) compat array-assign VARIABLES_VALUES '${token}' $value setglobal RESULT = ''"" } # # TYPES # proc variable::type { if [[ ${VARIABLES_DEBUG} == 1 ]] { stderr "variable::type $(@)" ; } declare index=$1 if test ! $(VARIABLES_METADATA[${index}]+isset) { stderr "The variable token [$(index)] does not exist" exit 1 } declare -a metadata=(${VARIABLES_METADATA[$index]}) setglobal RESULT = $(metadata[${VARIABLES_OFFSETS[type]}]) } proc _variable::type_p { variable::type $(@) echo $RESULT } proc variable::type::define { declare typeName="$(1)" declare -a typeParents=() # declare -g VARIABLES_TYPES=() if test $(VARIABLES_TYPES[${typeName}]+isset) { stderr "Variable type [$(typeName)] already defined" exit 1 } declare -a superTypes if test $(2+isset) { declare typeParent="$(2)" if test ! $(VARIABLES_TYPES[$typeParent]+true) { stderr "Variable type [$(typeName)] declare unknown parent type [$(typeParent)]" exit 1 } setglobal typeParents = ''("${typeParent}") setglobal typeParents = ''(${VARIABLES_TYPES[${typeParent}]}) compat array-assign VARIABLES_TYPES '${typeName}' $(typeParents[@]) } else { compat array-assign VARIABLES_TYPES '${typeName}' ''"" } } # # Returns true (0) if the variable is of the specified type or any of its supertypes # proc variable::type::instanceOf { declare token="$(1)" declare expectedType="$(2)" if variable::type::exactlyA $(token) $(expectedType) { return 0 } variable::type $(token) declare actualType="$(RESULT)" declare -a actualSuperTypes=(${VARIABLES_TYPES[$actualType]}) if test $(#actualSuperTypes[@]) -lt 1 { return 1 ; } declare superType for superType in [$(actualSuperTypes[@])] { if test $(expectedType) == $(superType) { return 0 } } return 1 } proc variable::type::instanceOfOrExit { declare valueToken="$(1)" declare expectedType="$(2)" if ! variable::type::instanceOf $(valueToken) $(expectedType) { variable::type $(valueToken) stderr "Variable [$(valueToken)] is not of type [$(expectedType)] (actual type [$(RESULT)])" exit 1 } } # # Returns true (0) if the variable is of the specified type # proc variable::type::exactlyA { declare token="$(1)" declare expectedType="$(2)" if test ! $(VARIABLES_TYPES[$expectedType]+true) { stderr "Unknown type [$(expectedType)]" exit 1 } variable::type $(token) declare actualType="$(RESULT)" if test $(actualType) == $(expectedType) { return 0 } else { return 1 } } proc variable::value { if [[ ${VARIABLES_DEBUG} == 1 ]] { stderr "variable::value $(@)" ; } declare index="$(1)" if ! test $(VARIABLES_VALUES[${index}]+isset) { stderr "The variable token [$(index)] does not exist" exit 1 } setglobal RESULT = $(VARIABLES_VALUES[${index}]) } proc _variable::value_p { variable::value $(@) echo $RESULT } proc variable::debug { declare token="$(1)" variable::type $token ; declare type=$RESULT if functionExists "variable::$(type)::debug" { eval "variable::$(type)::debug $(token)" setglobal RESULT = $RESULT return } if [[ -z ${VARIABLES_TYPES[${type}]} ]] { variable::debug::simple $token setglobal RESULT = $RESULT return } declare -a actualSuperTypes=(${VARIABLES_TYPES[$type]}) declare superType for superType in [$(actualSuperTypes[@])] { if functionExists "variable::$(superType)::debug" { eval "variable::$(superType)::debug $(token)" setglobal RESULT = $RESULT return } } variable::debug::simple $token setglobal RESULT = $RESULT } proc variable::debug::simple { declare token="$(1)" variable::type $(token) declare type="$(RESULT)" variable::value $(token) declare value="$(RESULT)" setglobal RESULT = ""$(type) :: $(value)"" } proc variable::debug::join { declare joinChar=$(1) if [[ ${#@} == 1 ]] { setglobal RESULT = ''"" return } if [[ ${#@} == 2 ]] { setglobal RESULT = $(2) return } declare -a items=("${@:2}") declare size declare max_index setglobal RESULT = $(2) sh-expr ' size=${#items[@]}, max_index=size-1 ' for (( i=1; i<=max_index; i+=1 )); do RESULT="${RESULT}${joinChar}${items[$i]}" done } proc variable::toSexp { declare token="$(1)" variable::type $token ; declare type=$RESULT if functionExists "variable::$(type)::toSexp" { eval "variable::$(type)::toSexp $(token)" setglobal RESULT = $RESULT return } if [[ -z ${VARIABLES_TYPES[${type}]} ]] { variable::debug $token setglobal RESULT = $RESULT return } declare -a actualSuperTypes=(${VARIABLES_TYPES[$type]}) declare superType for superType in [$(actualSuperTypes[@])] { if functionExists "variable::$(superType)::toSexp" { eval "variable::$(superType)::toSexp $(token)" setglobal RESULT = $RESULT return } } variable::debug $token setglobal RESULT = $RESULT } # # == Output # proc variable::printMetadata { stderr "VARIABLES_METADATA" declare keys setglobal keys = $[for var in [$(!VARIABLES_METADATA[@])] { echo $var; } | sort -n] for key in [$(keys)] { stderr " [$(key)]=[$(VARIABLES_METADATA[${key}])]" } stderr "VARIABLES_VALUES" setglobal keys = $[for var in [$(!VARIABLES_VALUES[@])] { echo $var; } | sort -n] for key in [$(keys)] { stderr " [$(key)]=[$(VARIABLES_VALUES[${key}])]" } stderr "VARIABLES_INDEX=$(VARIABLES_INDEX)" } proc variable::print { declare token=$1 declare indent=$2 variable::type $(token); declare type=$RESULT match $(type) { with list echo "$(indent)$(type)($(token)) :: [" variable::value $(token); declare -a values=($RESULT) # echo "${indent} ${values[@]}" for value in [$(values[@])] { variable::print $(value) "$(indent) " } echo "$(indent)]" # echo "${indent}${type} :: size=${#value[@]} :: ${value[@]}" with string echo "$(indent)$(type)($(token)) :: [$[_variable::value_p $(token)]]" with integer echo "$(indent)$(type)($(token)) :: [$[_variable::value_p $(token)]]" with * stderr "Invalid variable type [$(type)] for token [$(token)]" variable::printMetadata exit 1 } } # ====================================================== if test $0 != $BASH_SOURCE { return } declare testToken variable::type::define atom variable::type::define string atom variable::type::define number atom variable::type::define integer number # == ATOM TESTS == variable::new integer 12 ; \ declare atomId_1=$RESULT variable::type $atomId_1 ; \ assert::equals integer $RESULT Type of first atom variable::type $(atomId_1) ; \ assert::equals integer $RESULT Type of first atom variable::value $atomId_1 ; \ assert::equals 12 $RESULT Value of first atom variable::value $atomId_1 ; \ assert::equals 12 $RESULT Value of first atom variable::new string "hello there" ; \ declare atomId_2=$RESULT variable::type $atomId_2 ; \ assert::equals string $RESULT Type of second atom variable::value $atomId_2 ; \ assert::equals "hello there" $RESULT Value of second atom variable::value $atomId_1 ; \ assert::equals 12 $RESULT Value of first atom remains # exactlyA variable::new integer ; \ setglobal testToken = $(RESULT) variable::type $testToken variable::type::exactlyA $(testToken) integer assert::equals 0 $Status "exactlyA same" variable::type::exactlyA $(testToken) number assert::equals 1 $Status "exactlyA super" variable::type::exactlyA $(testToken) string assert::equals 1 $Status "exactlyA other" # instanceOf variable::new number ; \ setglobal testToken = $(RESULT) variable::type $testToken variable::type::instanceOf $(testToken) integer assert::equals 1 $Status "number instanceOf integer" variable::type::instanceOf $(testToken) number assert::equals 0 $Status "number instanceOf number" variable::type::instanceOf $(testToken) atom assert::equals 0 $Status "number instanceOf atom" variable::type::instanceOf $(testToken) string assert::equals 1 $Status "number instanceOf string" assert::report if test $(1+isset) && test $1 == "debug" { variable::printMetadata }