Login
7 branches 0 tags
Ben (Win10) Made [fmt] more in-line with what rust is doing d13a761 3 years ago 385 Commits
nujel / stdlib / string / format.nuj
; Subroutines and macros for formatted text output

[def fmt/tmp 123]

[defun fmt/valid-argument? [argument]
       [or [int? argument] [symbol? argument]]]

[def fmt/expr/count 0]
[defun fmt/expr [expr arguments-used]
       [when-not [string? expr]
                 [throw [list :format-error "fmt needs a string literal as a first argument, since it is implemented as a macro" expr [current-lambda]]]]
       [def split-expr  [split expr ":"]]
       [def argument    [car split-expr]]
       [def format-spec [or [cadr split-expr] ""]]

       [if [== "" argument]
           [do [array/set! arguments-used [-- fmt/expr/count] #t]
               [str->sym [string fmt/expr/count]]]
           [let [[read-vals [read argument]]]
                [when [cdr read-vals]
                      [throw [list :format-error "Format argument specifier contains more than a single atom" argument [current-lambda]]]]
                [when-not [fmt/valid-argument? [car read-vals]]
                          [throw [list :format-error "Format argument specifier should be either an integer or a symbol" argument [current-lambda]]]]

                [when [int? [car read-vals]]
                      [when [or [< [car read-vals] 0] [>= [car read-vals] [array/length arguments-used]]]
                            [throw [list :format-error "fmt numbered argument is out of bounds" argument [current-lambda]]]]
                      [array/set! arguments-used [car read-vals] #t]]
                [str->sym [string [car read-vals]]]]]]

[def fmt/args/map-fun/count 0]
[defun fmt/args/map-fun [arg]
       [def s [str->sym [string fmt/args/map-fun/count]]]
       [++ fmt/args/map-fun/count]
       [list 'def s arg]]

[defmacro fmt [format-string . args]
          "Return a formatted string"
          [when-not [string? format-string]
                    [throw [list :type-error "fmt needs a string literal as a first argument, since it is implemented as a macro" format-string [current-lambda]]]]
          [def cuts #nil]
          [for [i 0 [string/length format-string]]
               [case [char-at format-string i]
                     [#\{ [do [when [int? [car cuts]] [throw [list :format-error "fmt placeholders can't be nested" format-string [current-lambda]]]]
                              [set! cuts [cons i cuts]]]]
                     [#\} [do [when-not [int? [car cuts]] [throw [list :format-error "fmt expects all brackets to be closed" format-string [current-lambda]]]]
                              [set! cuts [cons [cons [car cuts] i] [cdr cuts]]]]]]]
          [when [int? [car cuts]] [throw [list :format-error "fmt placeholders can't be nested" format-string [current-lambda]]]]
          [def expr-list #nil]
          [def last-pos [string/length format-string]]
          [def arguments-used [-> [array/allocate [length args]] [array/fill! #f]]]

          [set! fmt/expr/count [array/length arguments-used]]
          [for-in [c cuts]
                  [def lit [substr format-string [+ [cdr c] 1] last-pos]]
                  [when-not [== "" lit] [set! expr-list [cons lit expr-list]]]
                  [def expr [fmt/expr [substr format-string [+ 1 [car c]] [cdr c]] arguments-used]]
                  [set! expr-list [cons expr expr-list]]
                  [set! last-pos [car c]]]
          [when [> last-pos 0]
                [def lit [substr format-string 0 last-pos]]
                [set! expr-list [cons lit expr-list]]]
          [for [i 0 [array/length arguments-used]]
               [when-not [array/ref arguments-used i]
                         [throw [list :format-error "fmt expects all arguments to be used" [list format-string [list/ref args i]] [current-lambda]]]]]
          [def expr [if [cdr expr-list]
                        [cons 'cat expr-list]
                        [if [string? [car expr-list]]
                            [car expr-list]
                            [cons 'string expr-list]]]]
          [set! fmt/args/map-fun/count 0]
          [if args
              `[let* ~@[map args fmt/args/map-fun]
                      ~expr]
              expr]]

[defmacro pfmt [format-string . args]
          "Print a formatted string"
          `[print [fmt ~format-string ~@args]]]

[defmacro efmt [format-string . args]
          "Print a formatted string"
          `[error [fmt ~format-string ~@args]]]