;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; executor: (lambda (argv) ... )
;;   argv is a vector (slice).
;;   return proper value.
;;
;; completer (lambda (str) ... )
;;   return completion-vector.
;;   completion vector could be empty, contain one item, or several items.
;;   one item will be taken as "good completion found".
;;
(begin-module 'concmd)
(module-export '(
  reset-define-stack
  new-simple-command
  new-simple-command-with-completer
  new-complex-command
  new-complex-command-with-completer
  end-complex-command
  ;;
  collect-completions
  ;;
  has-word?
  chop-word
  parse-word
  append-word
  ;;
  parse
  unparse
  ;;
  execute-vector
  execute-string
))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; parse and unparse command line

define need-quote?(str)
  cond
    {length(str) = 0} #t
    {str[0] = 34} #t
    else
      let loop <* idx 0 *>
        if {str[idx] <= 32}
          #t
          if {{idx + 1} = string-length(str)}
            #f
            loop {idx + 1}

define char->qch(ch)
  cond
    {ch = 9}  #\t
    {ch = 10} #\n
    {ch = 13} #\r
    {ch = 27} #\e
    {{ch = 34} or {ch = 92} or {ch < 32}} ch
    else #f

define qch->char(ch)
  cond
    {ch = #\t} 9
    {ch = #\r} 13
    {ch = #\n} 10
    {ch = #\e} 27
    else ch


define quote-str(str)
  if need-quote?(str)
    let loop <* idx 0 \\ res "\"" *>
      if {idx = string-length(str)}
        string-append(res 34)
        let* <* ch str[idx] \\ qq char->qch(ch) *>
          if {false? qq}
            loop {idx + 1} string-append(res ch)
            loop {idx + 1} string-append(res 92 qq)
    str

;; append word to the command line.
define append-word(str word)
  cond
    {string-length(str) = 0}              quote-str(word)
    {str[{string-length(str) - 1}] > 32}  string-append(str " " quote-str(word))
    else                                  string-append(str quote-str(word))


;; check if the string has something to parse
define has-word?(str)
  let loop <* idx 0 \\ str str *>
    cond
      {idx = string-length(str)}  #f
      {str[idx] > 32}             #t
      else                        loop({idx + 1} str)


;; parse first word in the string.
;; return string or #f if there are no words there.
define parse-word(str)
  define get-token(str)
    let loop <* idx 0 *>
      if {{idx = string-length(str)} or {str[idx] <= 32}}
        substring(str 0 idx)
        loop {idx + 1}
  ;;
  define get-quoted(str)
    let loop <* idx 1 \\ res "" *>
      if {idx = string-length(str)}
        res
        cond
          {str[idx] = 34}
            if {{{idx + 1} = string-length(str)} or {str[{idx + 1}] <= 32}}
              res
              loop {idx + 1} string-append(res 34)
          {str[idx] = 92}
            if {{idx + 1} = string-length(str)}
              string-append(res 92)
              loop {idx + 2} string-append(res qch->char(str[{idx + 1}]))
          else
            loop {idx + 1} string-append(res str[idx])
  ;;
  define get-word(str)
    cond
      {string-length(str) = 0}  #f
      {str[0] <= 32}            get-word(substring(str 1))
      {str[0] = 34}             get-quoted(str)
      else                      get-token(str)
  ;;
  get-word(str)


;; remove first word from the string.
;; this leaves spaces in place.
define chop-word(str)
  define trim-at(str idx)
    cond
      {idx >= string-length(str)} ""
      ;; don't
      ;{str[idx] <= 32} trim-at(str {idx + 1})
      else substring(str idx)
  ;;
  define get-token(str idx)
    cond
      {idx >= string-length(str)} ""
      {str[idx] > 32}             get-token(str {idx + 1})
      else                        trim-at(str idx)
  ;;
  define get-quoted(str idx)
    cond
      {idx >= string-length(str)} ""
      {str[idx] = 92}             get-quoted(str {idx + 2})
      {str[idx] = 34}             trim-at(str {idx + 1})
      else                        get-quoted(str {idx + 1})
  ;;
  define get-word(str idx)
    cond
      {idx >= string-length(str)} ""
      {str[idx] <= 32}            get-word(str {idx + 1})
      {str[idx] = 34}             get-quoted(str {idx + 1})
      else                        get-token(str idx)
  ;;
  get-word(str 0)


;; convert parsed vector back to string.
;; the resulting string could be properly parsed by "parse".
define unparse(vec)
  let loop <* idx 0 \\ res "" *>
    if {idx = vector-length(vec)}
      res
      loop {idx + 1} append-word(res vec[idx])


;; parse command line into vector.
;; last unclosed string argument is autoclosed
define parse(str)
  let loop
    \\
      word parse-word(str)
      str chop-word(str)
      vec make-vector()
    cond
      {false? word} vec
      else
        vector-push! vec word
        loop parse-word(str) chop-word(str) vec


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; command list management

;; command definition stack
;; command item: (handler help-str/help-proc . completer)
define cmdroot ::dict:new()

(define (cmd-handler cmd) (car cmd))
(define (cmd-help cmd) (cadr cmd))
(define (cmd-completer cmd) (cddr cmd))
(define (cmd-completer-set! cmd cpl) (cdr-set! (cdr cmp) cpl))

(define (cmd-new handler help cpl)  cons(handler cons(help cpl)))


constant stack-root cons(cmdroot #nil)

cdr-set! stack-root stack-root

;; stack for definitions
define stack stack-root


;; reset complex command definition stack
define reset-define-stack()
  gset! stack stack-root


define ?unique-command(name)
  assert {false? ::dict:has?(car(stack) name)}
    string-append("duplicate command handler for '" name "'")

define ?good-command-name(name)
  assert {symbol? name}
    string-append("command name should be a symbol, but it is " ::SYSTEM:type-name(name))

define fix-command-help(name help)
  cond
    {string? help}    help
    {procedure? help} help
    {nil? help}       "no help"
    {false? help}     "no help"
    else              error(string-append("invalid help for command '" name "'"))


;; define new command; "handler" is either procedure, or dict.
;; internal API.
define new-command(name help handler cpl)
  ?good-command-name name
  ;assert {{dict? handler} or {procedure? handler}}
  ;  string-append("bad command handler for '" symbol->string(name) "'")
  ?unique-command name
  ::dict:put! car(stack) name cmd-new(handler help cpl)


;; define new command handler
define new-simple-command(name help handler)
  ?good-command-name name
  assert {procedure? handler}
    string-append("bad command handler for '" name "'")
  new-command name fix-command-help(name help) handler #nil

define new-simple-command-with-completer(name help handler cpl)
  ?good-command-name name
  assert {procedure? handler}
    string-append("bad command handler for '" name "'")
  new-command name fix-command-help(name help) handler cpl

;; define new command with subcommands
define new-complex-command(name help)
  ?good-command-name name
  let <* nd ::dict:new() *>
    new-command name fix-command-help(name help) nd #nil
    gset! stack cons(nd stack)

define new-complex-command-with-completer(name help cpl)
  ?good-command-name name
  let <* nd ::dict:new() *>
    new-command name fix-command-help(name help) nd cpl
    gset! stack cons(nd stack)

;; finish defining last complex command
define end-complex-command()
  gset! stack cdr(stack)

;; add subcommands to the complex command.
;; use "end-complex-command" to finish.
define extend-complex-command(name)
  ?good-command-name name
  let <* nd ::dict:find(car(stack) name) *>
    assert {not {false? nd}}
      string-append("command handler '" name "' not found")
    assert {dict? nd}
      string-append("command '" name "' is not a complex one")
    gset! stack cons(nd stack)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; utilities

;; shellsort
;; "lessp" is "<"
define vector-sort(vec lessp)
  ;; this generates Tokuda sequence: h[n] := trunc(h[n-1] * 2.25 + 1)
  define tseq(n)
    define head cons(1 #nil)
    iterate
      repeat {car(head) < n}
        {head := cons({bit-shl(car(head)) + bit-shr(car(head) 2) + 1} head)}
      (else)
    head
  ;;
  ;; shellsort iteration
  define sort1(vec lessp step)
    define p
    define j
    define vlen vector-length(vec)
    define i step
    ;printf "sort1: step=%s\n" step
    while {i < vlen}
      {p := vec[i]}
      {j := i}
      while {{j >= step} and lessp(p vec[{j - step}])}
        {vec[j] := vec[{j - step}]}
        dec! j step
      {vec[j] := p}
      inc! i
  ;;
  ;; shellsort main loop
  if {vector-length(vec) > 1}
    begin
      define steps tseq(vector-length(vec))
      while {pair? steps}
        sort1(vec lessp car(steps))
        {steps := cdr(steps)}
  vec

;; proc(key value) => new-list-item (#nil will not be appended)
;define dict-map-to-vector(proc dict)
;  define res #nil
;  define vec make-vector()
;  define idx ::dict:iter-step(dict 0)
;  while {pair? idx}
;    {res := proc(caar(idx) cdar(idx))}
;    if {not {null? res}} vector-push!(vec res)
;    {idx := ::dict:iter-step(dict cdr(idx))}
;  vec
define dict-map-to-vector(proc dict)
  define res #nil
  define vec make-vector()
  define it ::dict:iter:new(dict)
  while ::dict:iter:next(it)
    if {not {null? {res := proc(::dict:iter:key(it) ::dict:iter:value(it))}}}
      vector-push!(vec res)
  vec

;; proc(value)
define vector-foreach(proc vec)
  cond
    {vector-length(vec) = 0} #f
    {vector-length(vec) = 1} proc(vec[0])
    else
      proc(vec[0])
      vector-foreach(proc vector-slice(vec 1))

;; proc(val prevval)
define vector-fold(proc vec)
  if {vector-length(vec) = 0}
    #f
    let loop <* idx 1 \\ res vec[0] *>
      if {idx = vector-length(vec)}
        res
        loop {idx + 1} proc(vec[idx] res)


define vector-longest-prefix(vec)
  define string-longest-prefix(s1 s2)
    define idx 0
    ;if {symbol? s1} {s1 := symbol->string(s1)}
    ;if {symbol? s2} {s2 := symbol->string(s2)}
    while {{idx <> string-length(s1)} and {idx <> string-length(s2)}}
      if {s1[idx] <> s2[idx]} ::SYSTEM:return(substring(s2 0 idx))
      inc! idx
    if {string-length(s1) < string-length(s2)}
      s1
      s2
  if {vector-length(vec) = 0}
    ""
    else
      ;define pfx vec[0]
      ;vector-foreach
      ;  lambda (str)
      ;    {pfx := string-longest-prefix(str pfx)}
      ;  vec
      ;pfx
      vector-fold string-longest-prefix vec


;; peform completion for the given dictionary
define collect-completions-in(str dict)
  if {symbol? str} {str := symbol->string(str)}
  vector-sort
    dict-map-to-vector
      lambda (name value)
        let <* sname symbol->string(name) *>
          if {{length(sname) >= length(str)} and
              {substring(sname 0 length(str)) string-ci=? str}}
            sname
            #nil
      dict
    string-ci<?


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; autocompletion

;; return pair: (newstr . completion-vector).
;; append space if only one completion is found.
;; autocompletes only the last element.
;; no changes if the string ends with a blank.
define collect-completions(str)
  define string-prefix?(pfx str)
    if {string-length(pfx) > string-length(str)}
      #f
      string-ci=?(pfx substring(str 0 string-length(pfx)))
  ;;
  define copy-vector(vec need-copy?)
    if need-copy?
      then
        define idx 0
        define len vector-length(vec)
        define v2 make-vector(len)
        while {idx <> len}
          {v2[idx] := vec[idx]}
          inc! idx
        v2
      else
        vec
  ;;
  ;define dump-vector(vec)
  ;  printf "::: vlen=%d :::\n" vector-length(vec)
  ;  define idx 0
  ;  define len vector-length(vec)
  ;  while {idx <> len}
  ;    printf "  %d: %o\n" idx vec[idx]
  ;    inc! idx
  ;;
  define cpl-filter(pfx cpl)
    let cpl-filter-intr <* pfx pfx \\ cpl cpl \\ need-copy? #t *>
      cond
        {symbol? cpl} cpl-filter-intr(pfx symbol->string(cpl) #f)
        {number? cpl} cpl-filter-intr(pfx number->string(cpl) #f)
        {string? cpl} cpl-filter-intr(pfx make-vector(1 cpl) #f)
        {pair? cpl}   cpl-filter-intr(pfx list->vector(cpl) #f)
        {vector? cpl}
          if {string-length(pfx) = 0}
            vector-sort(copy-vector(cpl need-copy?) string-ci<?)
            let loop <* idx 0 \\ vec make-vector() *>
              cond
                {idx = vector-length(cpl)}
                  ;dump-vector vec
                  vector-sort(vec string-ci<?)
                string-prefix?(pfx cpl[idx])
                  vector-push! vec cpl[idx]
                  loop {idx + 1} vec
                else
                  loop {idx + 1} vec
        else make-vector()
  ;;
  define append-space(str)
    if {{string-length(str) <> 0} and {str[{string-length(str) - 1}] > 32}}
      string-append(str " ")
      str
  ;;
  define append-word-x(str word)
    if {string-length(word) = 0}
      str
      append-word(str word)
  ;;
  define complete-empty-str()
    let <* cpl collect-completions-in("" cmdroot) *>
      cond
        {vector-length(cpl) = 0}  cons("" cpl)
        {vector-length(cpl) = 1}  cons(append-space(quote-str(cpl[0])) make-vector())
        else                      cons(append-word-x("" vector-longest-prefix(cpl)) cpl)
  ;;
  define complete-str(str vec)
    define complete-last(str newstr cpl)
      cond
        {vector-length(cpl) = 0}  cons(str cpl)
        {vector-length(cpl) = 1}  cons(append-space(append-word(newstr cpl[0])) make-vector())
        else                      cons(append-word-x(newstr vector-longest-prefix(cpl)) cpl)
    ;;
    define use-completer(str newstr pfx cmd)
      complete-last(str newstr cpl-filter(pfx cmd-completer(cmd)(pfx)))
    ;; traverse command chain
    let loop <* idx 0 \\ dict cmdroot \\ last-cmd #nil \\ newstr "" *>
      if {idx = vector-length(vec)}
        cond
          {null? last-cmd}                      cons(str make-vector())
          {procedure? cmd-completer(last-cmd)}  use-completer(str newstr "" last-cmd)
          {dict? cmd-handler(last-cmd)}
            complete-last(str newstr collect-completions-in("" cmd-handler(last-cmd)))
          else                                  cons(append-space(newstr) make-vector())
        let <* cmd ::dict:find-nf(dict string->symbol(vec[idx]) #f) *>
          cond
            {false? cmd}
              if {idx = {vector-length(vec) - 1}}
                cond
                  {{not {null? last-cmd}} and {procedure? cmd-completer(last-cmd)}}
                    use-completer(str newstr vec[idx] last-cmd)
                  else
                    complete-last(str newstr collect-completions-in(vec[idx] dict))
                cons(str make-vector())
            {dict? cmd-handler(cmd)}
              loop {idx + 1} cmd-handler(cmd) cmd append-space(append-word(newstr vec[idx]))
            {idx = {vector-length(vec) - 1}}
              loop {idx + 1} #nil cmd append-word(newstr vec[idx])
            {{procedure? cmd-completer(cmd)} and {idx = {vector-length(vec) - 2}}}
              use-completer(str append-space(append-word(newstr vec[idx])) vec[{idx + 1}] cmd)
            else
              cons(str make-vector())
  ;; main code
  assert({string? str} "command line should be a string")
  cond
    {string-length(str) = 0}  complete-empty-str()
    else                      complete-str(str parse(str))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; "help" support

define cmd-print-help(vec)
  if {vector-length(vec) = 0}
    writeln "print help for the given command.\nwhat command are you interested in?"
    let loop <* idx 0 \\ dict cmdroot *>
      if {idx = vector-length(vec)}
        writeln "ERROR: unknown command"
        let <* cmd ::dict:find-nf(dict string->symbol(vec[idx]) #f) *>
          if {idx <> 0} write(" ")
          write vec[idx]
          if {{not {false? cmd}} and {dict? cmd-handler(cmd)}}
            if {{idx + 1} = vector-length(vec)}
              let <* help cmd-help(cmd) *>
                write ": "
                if {procedure? help}
                  writeln help()
                  writeln help
              loop {idx + 1} cmd-handler(cmd)
            if {false? cmd}
              writeln ": unknown command"
              let <* help cmd-help(cmd) *>
                write ": "
                if {procedure? help}
                  writeln help()
                  writeln help

new-simple-command 'help "print help for the given command" cmd-print-help


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; command line execution

;; execute parsed command string
define execute-vector(vec)
  if {vector-length(vec) = 0}
    #f
    let loop <* idx 0 \\ dict cmdroot *>
      if {idx = vector-length(vec)}
        then
          writeln "ERROR: unknown command"
          #f
        let <* cmd ::dict:find-nf(dict string->symbol(vec[idx]) #f) *>
          if {{not {false? cmd}} and {dict? cmd-handler(cmd)}}
            loop {idx + 1} cmd-handler(cmd)
            cond
              {false? cmd}
                writeln "ERROR: unknown command"
              {dict? cmd-handler(cmd)}
                writeln "ERROR: subcommand expected"
              {{idx + 1} < vector-length(vec)}
                cmd-handler(cmd)(vector-slice(vec {idx + 1}))
              else
                cmd-handler(cmd)(vector-slice(vec 0 0))


define execute-string(str)
  execute-vector parse(str)


(end-module 'concmd)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; console print buffer
define debug-printf(... rest)
  define old-hook SYSTEM:print-hook-set!(#nil)
  apply printf rest
  SYSTEM:print-hook-set! old-hook


(define con-print-buf make-vector(256 ""))
(define con-pbuf-current 0)

define con-buffer-length()
  vector-length con-print-buf

define con-str-wrap(str width)
  define res make-vector()
  ;;
  define push-str(str)
    vector-push! res str
  ;;
  define non-wrap-char?(ch)
    (or between?(ch #\A #\Z)
        between?(ch #\a #\z))
  ;;
  define find-wrap-pos(str width)
    cond
      {string-length(str) <= width}
        push-str str
        #nil
      else
        define pos {width - 1}
        ;debug-printf "ww(%d): %s ch=%c; ww=%o\n" width str str[pos] non-wrap-char?(str[pos])
        while {{positive? pos} and non-wrap-char?(str[{pos - 1}])}
          dec! pos
          ;debug-printf "  pos=%d; ch=%c; ww=%o\n" pos str[pos] non-wrap-char?(str[pos])
        if {not {positive? pos}}
          {pos := width}
        push-str substring(str 0 pos)
        if {pos = string-length(str)}
          #nil
          substring(str pos)
  ;;
  if {width <= 0} {width := (fixnum-max)}
  while {string? str}
    {str := find-wrap-pos(str width)}
  res

define con-buffer-string(idx width)
  {idx := {con-pbuf-current - idx - 1}}
  if {negative? idx}
    inc! idx vector-length(con-print-buf)
  con-str-wrap con-print-buf[idx] width

define con-print-hook()
  define active #t
  define str-bvec make-bytevector()
  define str-bvec-pos 0
  lambda (ch)
    if active
      if {ch = 10}
        then
          {active := #f}
          define str (if (positive? str-bvec-pos)
                         bytevector->string(bytevector-slice(str-bvec 0 str-bvec-pos))
                         "")
          {con-print-buf[con-pbuf-current] := str}
          printf "%s\n" str
          {str-bvec-pos := 0}
          gset! con-pbuf-current {{con-pbuf-current + 1} mod vector-length(con-print-buf)}
          {con-print-buf[con-pbuf-current] := ""}
          {active := #t}
        else
          if {str-bvec-pos = bytevector-length(str-bvec)}
            bytevector-push! str-bvec ch
            {str-bvec[str-bvec-pos] := ch}
          inc! str-bvec-pos
    active

(SYSTEM:print-hook-set! con-print-hook())

:::EOF:::

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; test code
#|
define print-vector(vec)
  printf "count=%d\n" vector-length(vec)
  let vloop <* idx 0 \\ vec vec *>
    if {idx <> vector-length(vec)}
      then
        printf "  %d: %o\n" idx vec[idx]
        vloop {idx + 1} vec

define do-test(argv)
  printf "do-test: args (%d)=%o\n" length(argv) argv
  print-vector argv

define do-xcmd-init(argv)
  printf "do-xcmd-init: args (%d)=%o\n" length(argv) argv
  print-vector argv

define do-xcmd-reset(argv)
  printf "do-xcmd-reset: args (%d)=%o\n" length(argv) argv
  print-vector argv

define do-xcmd-xcpl(argv)
  printf "do-xcmd-xcpl args (%d)=%o\n" length(argv) argv
  print-vector argv

define xcpl-completer(str)
  printf "xcpl str=%o\n" str
  #("a" "b" "aa" "c" "d" "e")


(concmd:new-simple-command 'test "test command #1" do-test)
(concmd:new-simple-command 'goo (lambda () "test command #2") do-test)
(concmd:new-simple-command 'boo "test command #3" do-test)
(concmd:new-simple-command 'test2 "test command #4" do-test)
(concmd:new-complex-command 'xcmd "complex command #1")
(concmd:new-simple-command 'init "xcmd init command" do-xcmd-init)
(concmd:new-simple-command 'reset "xcmd reset command" do-xcmd-reset)
(concmd:new-simple-command-with-completer 'cpl "xcmd command with custom completer" do-xcmd-xcpl xcpl-completer)
(concmd:end-complex-command)

;(print-vector concmd:collect-completions("te"))

(define vv concmd:parse("xc ini   42 \"goo d\\\"oo\" 6\"9 abc\"d e"))
(print-vector vv)
(printf "%o\n" concmd:unparse(vv))


let loop <* str "xc ini   42 \"goo d\\\"oo\" 6\"9 abc\"d e  " *>
  define word concmd:parse-word(str)
  printf "word=%o; str=%o\n" word str
  if {not {false? word}}
    loop concmd:chop-word(str)


define print-completion-result(cpl)
  cond
    {string? cpl}
      printf "str=%o\n" cpl
    else
      assert {string? car(cpl)} "oops!"
      assert {vector? cdr(cpl)} "oops!"
      printf "str=%o\n" car(cpl)
      let vloop <* idx 0 \\ vec cdr(cpl) *>
        if {idx <> vector-length(vec)}
          then
            printf "  %s\n" vec[idx]
            vloop {idx + 1} vec

define print-completion(str)
  writeln "================================="
  printf "cmdline: %o\n" str
  print-completion-result concmd:collect-completions(str)


(writeln "############# COMPLETIONS #############")
(print-completion "t")
;(print-vector (concmd:collect-completions "t"))
;(print-vector (concmd:collect-completions ""))
(print-completion "")
(print-completion "tx")
(print-completion "go")
(print-completion "xc")
(print-completion "xcmd ")
(print-completion "xcmd cp")
(print-completion "xcmd cpl")
(print-completion "xcmd cpl a")
(print-completion "xcmd cpl b")
(print-completion "xcmd cpl x")
;:::EOF:::

(concmd:execute-string "test")
(concmd:execute-string "test 69")
(concmd:execute-string "xcmd init")
(concmd:execute-string "xcmd init 69")
(concmd:execute-string "help test")
(concmd:execute-string "help xcmd")
(concmd:execute-string "help xcmd reset")

;(print-completion-result (concmd:completions-from-vector "t" #("tea" "test" "tess")))
|#
