From 5c9c5ed6e16ce2d5167862efd0588cafbf2e64aa Mon Sep 17 00:00:00 2001 From: Yogthos Date: Mon, 22 Jun 2026 18:04:51 -0400 Subject: [PATCH] Conformance: String/format static + java.text.NumberFormat Part of the java.* host-class gap (jolt-1nnn). String/format delegates to the core format engine; NumberFormat getInstance/getNumberInstance/getIntegerInstance group the integer part and honor min/max fraction digits. --- host/chez/host-static.ss | 50 +++++++++++++++++++++++++++++++++++++++- test/chez/unit.edn | 2 ++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/host/chez/host-static.ss b/host/chez/host-static.ss index 9ae7ecb..2b0c94c 100644 --- a/host/chez/host-static.ss +++ b/host/chez/host-static.ss @@ -259,8 +259,56 @@ (cons "isWhitespace" (lambda (c) (char<=? (integer->char (char-code c)) #\space))))) ;; String/valueOf(Object): "null" for nil, else jolt's str semantics. +;; String/format(fmt args…) / (locale fmt args…) -> the clojure.core format engine. (register-class-statics! "String" - (list (cons "valueOf" (lambda (x . _) (if (jolt-nil? x) "null" (jolt-str-render-one x)))))) + (list (cons "valueOf" (lambda (x . _) (if (jolt-nil? x) "null" (jolt-str-render-one x)))) + (cons "format" (lambda (a . rest) + (if (and (jhost? a) (string=? (jhost-tag a) "locale")) + (apply jolt-format (car rest) (cdr rest)) + (apply jolt-format a rest)))))) + +;; ---- java.text.NumberFormat (jolt-1nnn) ------------------------------------- +;; A grouping decimal formatter (selmer number-format / cuerdas). state: +;; #(grouping? min-frac max-frac). .format groups the integer part with commas. +(define (nf-make grouping? minf maxf) (make-jhost "numberformat" (vector grouping? minf maxf))) +(define (group-int-str s) ; "1234567" -> "1,234,567" + (let* ((neg (and (> (string-length s) 0) (char=? (string-ref s 0) #\-))) + (digs (if neg (substring s 1 (string-length s)) s)) + (n (string-length digs)) (out '())) + (let loop ((i 0)) + (when (< i n) + (when (and (> i 0) (= 0 (modulo (- n i) 3))) (set! out (cons #\, out))) + (set! out (cons (string-ref digs i) out)) (loop (+ i 1)))) + (string-append (if neg "-" "") (list->string (reverse out))))) +(define (nf-format self x) + (let* ((grouping? (vector-ref (jhost-state self) 0)) + (minf (vector-ref (jhost-state self) 1)) (maxf (vector-ref (jhost-state self) 2)) + (neg (< x 0)) (ax (abs (exact->inexact x))) + (scale (expt 10 maxf)) + (scaled (exact (round (* ax scale)))) + (ipart (quotient scaled scale)) (fpart (remainder scaled scale)) + (istr (number->string ipart)) + (fstr0 (if (> maxf 0) (let ((s (number->string fpart))) + (string-append (make-string (max 0 (- maxf (string-length s))) #\0) s)) "")) + ;; trim trailing zeros down to minf + (fstr (let loop ((s fstr0)) (if (and (> (string-length s) minf) + (char=? (string-ref s (- (string-length s) 1)) #\0)) + (loop (substring s 0 (- (string-length s) 1))) s)))) + (string-append (if neg "-" "") (if grouping? (group-int-str istr) istr) + (if (> (string-length fstr) 0) (string-append "." fstr) "")))) +(register-host-methods! "numberformat" + (list (cons "format" (lambda (self n) (nf-format self n))) + (cons "setMaximumFractionDigits" (lambda (self d) (vector-set! (jhost-state self) 2 (jnum->exact d)) jolt-nil)) + (cons "setMinimumFractionDigits" (lambda (self d) (vector-set! (jhost-state self) 1 (jnum->exact d)) jolt-nil)) + (cons "setGroupingUsed" (lambda (self b) (vector-set! (jhost-state self) 0 (jolt-truthy? b)) jolt-nil)))) +(register-class-statics! "NumberFormat" + (list (cons "getInstance" (lambda _ (nf-make #t 0 3))) + (cons "getNumberInstance" (lambda _ (nf-make #t 0 3))) + (cons "getIntegerInstance" (lambda _ (nf-make #t 0 0))))) +(register-class-statics! "java.text.NumberFormat" + (list (cons "getInstance" (lambda _ (nf-make #t 0 3))) + (cons "getNumberInstance" (lambda _ (nf-make #t 0 3))) + (cons "getIntegerInstance" (lambda _ (nf-make #t 0 0))))) (register-class-statics! "Class" ;; an array descriptor ("[C", "[I", …) is its own class token (so instance? and diff --git a/test/chez/unit.edn b/test/chez/unit.edn index a6429ae..9c2ed22 100644 --- a/test/chez/unit.edn +++ b/test/chez/unit.edn @@ -108,6 +108,8 @@ {:suite "sdf" :expr "(let [f (java.text.SimpleDateFormat. \"yyyy-MM-dd\")] (.format f (.parse f \"2023-07-15\")))" :expected "2023-07-15"} {:suite "reader" :expr "(let [r (-> \"foo bar baz\" java.io.StringReader. java.io.PushbackReader.)] [(read r) (slurp r)])" :expected "[foo bar baz]"} {:suite "reader" :expr "(let [r (-> \"(+ 1 2) 99\" java.io.StringReader. java.io.PushbackReader.)] [(read r) (read r)])" :expected "[(+ 1 2) 99]"} + {:suite "hostctor" :expr "(String/format \"%d-%s\" 5 \"x\")" :expected "5-x"} + {:suite "hostctor" :expr "[(.format (java.text.NumberFormat/getInstance) 1234567.5) (.format (java.text.NumberFormat/getIntegerInstance) 1234567)]" :expected "[1,234,567.5 1,234,567]"} {:suite "dotform" :expr "(.equals \"a\" \"a\")" :expected "true"} {:suite "dotform" :expr "(.equals \"a\" \"b\")" :expected "false"} {:suite "dotform" :expr "(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)" :expected "v=41"}