#
# mal (Make Lisp) Parser/Reader
#

if [ -z "${__mal_readerr_included__}" ]; then
__mal_readerr_included=true

source $(dirname $0)/types.sh

READ_ATOM () {
    local token=${__reader_tokens[${__reader_idx}]}
    __reader_idx=$(( __reader_idx + 1 ))
    case "${token}" in
        [0-9]*)  _number "${token}" ;;
        -[0-9]*) _number "${token}" ;;
        \"*)    if [[ ! "${token}" =~ ^\"(\\.|[^\\\"])*\"$ ]]; then
                    _error "expected '\"', got EOF"
                    return
                fi
                token="${token:1:-1}"
                token="${token//\\\\/${__keyw}}"
                token="${token//\\\"/\"}"
                token="${token//\\n/$'\n'}"
                token="${token//${__keyw}/\\}"
                _string "${token}" ;;
        :*)     _keyword "${token:1}" ;;
        nil)    r="${__nil}" ;;
        true)   r="${__true}" ;;
        false)  r="${__false}" ;;
        *)      _symbol "${token}" ;;
    esac
}

# Return seqence of tokens into r.
#   ${1}: Type of r (vector, list)
#   ${2}: starting symbol
#   ${3}: ending symbol
READ_SEQ () {
    local start="${1}"
    local end="${2}"
    local items=""
    local token=${__reader_tokens[${__reader_idx}]}
    __reader_idx=$(( __reader_idx + 1 ))
    if [[ "${token}" != "${start}" ]]; then
        r=
        _error "expected '${start}'"
        return
    fi
    token=${__reader_tokens[${__reader_idx}]}
    while [[ "${token}" != "${end}" ]]; do
        if [[ ! "${token}" ]]; then
            r=
            _error "expected '${end}', got EOF"
            return
        fi
        READ_FORM
        items="${items} ${r}"
        token=${__reader_tokens[${__reader_idx}]}
    done
    __reader_idx=$(( __reader_idx + 1 ))
    r="${items:1}"
}

# Return form in r
READ_FORM () {
    local token=${__reader_tokens[${__reader_idx}]}
    case "${token}" in
        \')     __reader_idx=$(( __reader_idx + 1 ))
                _symbol quote; local q="${r}"
                READ_FORM; local f="${r}"
                _list "${q}" "${f}" ;;
        \`)     __reader_idx=$(( __reader_idx + 1 ))
                _symbol quasiquote; local q="${r}"
                READ_FORM; local f="${r}"
                _list "${q}" "${f}" ;;
        \~)     __reader_idx=$(( __reader_idx + 1 ))
                _symbol unquote; local q="${r}"
                READ_FORM; local f="${r}"
                _list "${q}" "${f}" ;;
        \~\@)   __reader_idx=$(( __reader_idx + 1 ))
                _symbol splice-unquote; local q="${r}"
                READ_FORM; local f="${r}"
                _list "${q}" "${f}" ;;
        ^)      __reader_idx=$(( __reader_idx + 1 ))
                _symbol with-meta; local wm="${r}"
                READ_FORM; local meta="${r}"
                READ_FORM; local obj="${r}"
                _list "${wm}" "${obj}" "${meta}" ;;
        @)     __reader_idx=$(( __reader_idx + 1 ))
                _symbol deref; local d="${r}"
                READ_FORM; local f="${r}"
                _list "${d}" "${f}" ;;
        \))     _error "unexpected ')'" ;; 
        \()     READ_SEQ "(" ")"
                _list ${r} ;;
        \])     _error "unexpected ']'" ;; 
        \[)     READ_SEQ "[" "]"
                _vector ${r} ;;
        \})     _error "unexpected '}'" ;; 
        \{)     READ_SEQ "{" "}"
                _hash_map ${r} ;;
        *)      READ_ATOM
    esac
}

TOKEN_PAT=$'^^([][{}\\(\\)^@])|^(~@)|^("(\\\\.|[^\\"])*"?)|^(;[^\n]*)|^([~\'`])|^([^][ ~`\'";{}\\(\\)^@,\n]+)|^(,)|^([[:space:]]+)'

# Returns __reader_tokens as an indexed array of tokens
TOKENIZE () {
    local data="${*}"
    local datalen=${#data}
    local idx=0
    local chunk=0
    local chunksz=500
    local token=
    local str=

    __reader_idx=0
    declare -a -g __reader_tokens=()  # global array
    while true; do
        if (( ${#str} < ( chunksz / 2) )) && (( chunk < datalen )); then
            str="${str}${data:${chunk}:${chunksz}}"
            chunk=$(( chunk + ${chunksz} ))
        fi
        (( ${#str} == 0 )) && break
        [[ "${str}" =~ ${TOKEN_PAT} ]]
        token=${BASH_REMATCH[0]}
        str="${str:${#token}}"
        token="${token}"
        #echo "MATCH: '${token}' / [${str}]"
        if ! [[ "${token}" =~ (^[,]$|^[[:space:]]*;.*$|^[[:space:]]*$) ]]; then 
            __reader_tokens[${idx}]="${token}"
            idx=$(( idx + 1 ))
        fi
        if [ -z "${token}" ]; then
            _error "Tokenizing error at: ${str:0:50}"
            return 1
        fi
    done
}

# read-str from a raw "string" or from a string object. Retruns object
# read in r.
READ_STR () {
    declare -a __reader_tokens
    TOKENIZE "${*}" || return 1  # sets __reader_tokens
    #set | grep ^__reader_tokens
    if [ -z "${__reader_tokens[0]}" ]; then
        r=
        return 1  # No tokens
    fi
    READ_FORM
    #echo "Token: ${r}: <${ANON["${r}"]}>"
    return
}

# Call readline and save the history. Returns the string read in r.
READLINE_EOF=
READLINE_HISTORY_FILE=${HOME}/.mal-history
READLINE () {
    history -r "${READLINE_HISTORY_FILE}" 2>/dev/null || true
    read -r -e -p "${1}" r || return "$?"
    history -s -- "${r}"
    history -a "${READLINE_HISTORY_FILE}" 2>/dev/null || true
}

fi
