Carp/core/Format.carp
scottolsen b94b49bf86 Implement private
Even though `private?` has been around for a while, and we document the
behavior of `private` as marking a binding as private to a module--it
was never implemented as far as I can tell.

This implements private by adding a simple check to the evaluator. If a
binding is found in the global context, we check if it's marked as
private. If so, we inform the user that it can't be called from outside
of its module.

Note that we don't perform this check if the binding is found in the
internal env, since that means it's a function called within the same
module and thus is ok (even if marked as private).

After this change, something like the following works, granting us
proper encapsulation:

```
;; File Foo.carp
(deftype Foo [bar Int])

(private Foo.bar)

(defmodule Foo
  (defn get [foo]
    (Foo.bar foo))
)

;; Enter REPL
(load "Foo.carp")
(Foo.bar &(Foo.init 1))
The binding: Foo.bar is private; it may only be used within the module
that defines it. at REPL:1:2.
@(Foo.get &(Foo.init 1))
Compiled to 'out/Untitled' (executable)
1
=> 0
```

N.B. I also had to remove a private declaration from fmt-internal--this
declaration didn't really make much sense anyway, as fmt-internal is a
global function, so module-based privacy is not enforceable.
2020-06-19 10:30:55 -04:00

44 lines
2.0 KiB
Plaintext

(hidden fmt-internal)
(defndynamic fmt-internal [s args]
(let [idx (String.index-of s \%)
len (String.length s)]
(if (= idx -1)
(if (= (length args) 0)
s ; no more splits found, just return string
(macro-error
(str "error in format string: too many arguments to format string (missing directive for '"
(car args)
"')")))
(if (= len 1)
(macro-error "error in format string: expected expression after last %")
(if (= \% (String.char-at s (inc idx))) ; this is an escaped %
(list 'ref
(list 'String.append
"%"
(fmt-internal (String.slice s (+ idx 2) len) args)))
(if (= 0 (length args)) ; we need to insert something, but have nothing
(macro-error
(str "error in format string: not enough arguments to format string (missing argument for '%"
(String.slice s (inc idx) (inc (inc idx)))
"')"))
; okay, this is the meat:
; get the next % after our escaper
(let [next (String.index-of (String.slice s (inc idx) len) \%)]
(if (= -1 next)
(if (< 1 (length args))
(macro-error
(str "error in format string: too many arguments to format string (missing directive for '"
(cadr args)
"')"))
(list 'ref (list 'format s (car args))))
(let [slice (String.slice s 0 (+ (inc idx) next))]
(list 'ref
(list 'String.append
(list 'ref (list 'format slice (car args)))
(fmt-internal (String.slice s (+ (inc idx) next) len)
(cdr args)))))))))))))
(doc fmt "formats a string. It supports all of the string interpolations defined in format of the type that should be interpolated (e.g. %d and %x on integers).")
(defmacro fmt [s :rest args]
(list 'copy (fmt-internal s args)))