(define-module (sph lang sc-format))

(use-modules ((srfi srfi-1) #:select (split-at)) (ice-9 match)
  (sph) (sph hashtable)
  (sph lang scm-format) (sph lang scm-format format) (sph string) ((sph list) #:select (map-slice)))

(export sc-format sc-f scd-f sc-format-config sc-formatters)

(define sph-lang-sc-format-description
  "formatter for sc (sph-sc) source code. also serves as an example of how custom formatters can be defined")

(define (map-recurse recurse a indent) (map (l (a) (first (recurse a indent))) a))
(define sc-f format-list-f)

(define (sc-docstring a recurse config indent)
  (if (null? a) a
    (if (string? (first a))
      (pair
        (format-docstring (first a) (ht-ref-q config docstring-offset-doublequote)
          (ht-ref-q config indent-string) indent)
        (map-recurse recurse (tail a) indent))
      (map-recurse recurse a indent))))

(define (scd-f start mid end docstring-offset)
  (l (a recurse config indent)
    (list
      (format-list
        (apply-values
          (l (prefix suffix) (append prefix (sc-docstring suffix recurse config indent)))
          (split-at a docstring-offset))
        config indent start mid end)
      #f)))

(define (format-define a recurse config indent)
  (match a ((_ (name ...) types body) ((scd-f 3 1 0 3) a recurse config indent))
    ((_ (name ...) types body ...) ((scd-f 3 1 1 3) a recurse config indent))
    ((_ name/type/value ...) ((sc-f 1 3 3) a recurse config indent))))

(define (format-set a recurse config indent)
  (let (config (ht-copy* config (l (a) (ht-set-multiple! a (q max-exprs-per-line-assoc) 2))))
    (match a ((_ name value) ((sc-f 2 1 0) a recurse config indent))
      ((_ name/type ...) ((sc-f 1 2 2) a recurse config indent)))))

(define (format-sc-comment a recurse config indent)
  (list
    (format-list
      (pair (first a)
        (map
          (l (a)
            (if (string? a)
              (format-docstring a (ht-ref-q config docstring-offset-doublequote)
                (ht-ref-q config indent-string) indent)
              (first (recurse a indent))))
          (tail a)))
      config indent 1 1 1)
    #f))

"(sc-f 1 1 0) is the default"

(define sc-formatters
  (ht-create-symbol-q sc-define-syntax* (scd-f 2 1 1 2)
    begin (scd-f 1 1 1 1)
    sc-comment format-sc-comment
    for (sc-f 2 1 1)
    case (sc-f 3 1 1)
    case* (sc-f 3 1 1)
    declare format-set
    define format-define
    do-while (sc-f 2 1 1)
    if (sc-f 3 1 1)
    if* (sc-f 3 1 1)
    label (sc-f 2 1 1)
    pre-define format-set
    pre-define-if-not-defined format-set
    pre-include-once (sc-f 1 2 2)
    pre-let format-let
    range-comment format-range-comment
    sc-include-once (sc-f 1 2 2)
    scsh-block-comment format-scsh-block-comment
    semicolon-comment format-semicolon-comment
    set format-set
    struct (sc-f 1 1 1)
    struct-pointer-set (sc-f 2 2 2) struct-set (sc-f 2 2 2) array-set (sc-f 2 2 2) while (sc-f 2 1 1)))

(define sc-format-config
  (ht-create-symbol-q indent-string (string-multiply " " 2)
    max-chars-per-line 100
    max-exprs-per-line-start 3
    max-exprs-per-line-middle 3
    max-exprs-per-line-end (inf)
    max-exprs-per-line-assoc 1
    docstring-offset-doublequote #t
    multiple-leading-parenthesis-spacing #t
    toplevel-vertical-spacing 1 toplevel-vertical-spacing-oneline 0))

(define sc-format-default-config
  (ht-create-symbol-q descend-prefix->format-f sc-formatters format sc-format-config))

(define* (sc-format a #:key (indent 0) (config sc-format-default-config))
  (let
    ( (result (map (l (a) (first (scm-format-list->string a indent config))) a))
      (config-format (ht-tree-ref config (q format))))
    (string-join-with-vertical-spacing result ""
      (ht-ref config-format (q toplevel-vertical-spacing))
      (ht-ref config-format (q toplevel-vertical-spacing-oneline)))))
