mirror of
https://github.com/carp-lang/Carp.git
synced 2024-09-17 08:27:45 +03:00
feat: add fstr (#1142)
* feat: add fstr * test: add fstr test * fix: memory error in test * fix: fix backslash parser * feat: add octal escape literals
This commit is contained in:
parent
63291c53af
commit
8c1999d656
@ -26,7 +26,10 @@
|
|||||||
(defndynamic suffix [s from]
|
(defndynamic suffix [s from]
|
||||||
(String.slice s from (String.length s)))
|
(String.slice s from (String.length s)))
|
||||||
|
|
||||||
(defndynamic tail [s ]
|
(defndynamic head [s]
|
||||||
|
(String.prefix s 1))
|
||||||
|
|
||||||
|
(defndynamic tail [s]
|
||||||
(String.suffix s 1))
|
(String.suffix s 1))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -41,3 +41,67 @@
|
|||||||
(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).")
|
(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]
|
(defmacro fmt [s :rest args]
|
||||||
(list 'copy (fmt-internal s args)))
|
(list 'copy (fmt-internal s args)))
|
||||||
|
|
||||||
|
(hidden f-parse-expr-string)
|
||||||
|
(defndynamic f-parse-expr-string [s r]
|
||||||
|
(if (= (String.length s) 0)
|
||||||
|
-1
|
||||||
|
(let [h (String.head s)]
|
||||||
|
(if (= h "\"")
|
||||||
|
(inc r)
|
||||||
|
(let [i (if (= h "\\") 2 1)]
|
||||||
|
(f-parse-expr-string
|
||||||
|
(String.suffix s i)
|
||||||
|
(+ r i)))))))
|
||||||
|
|
||||||
|
(hidden f-parse-expr)
|
||||||
|
(defndynamic f-parse-expr [s idx r]
|
||||||
|
(cond
|
||||||
|
(= idx 0) r
|
||||||
|
(= (String.length s) 0) -1
|
||||||
|
|
||||||
|
(let [h (String.head s)
|
||||||
|
t (String.tail s)]
|
||||||
|
(if (= h "\"")
|
||||||
|
(let [l (f-parse-expr-string t 0)]
|
||||||
|
(if (= l -1)
|
||||||
|
-1
|
||||||
|
(f-parse-expr (String.suffix s (inc l)) idx (+ r (inc l)))))
|
||||||
|
(f-parse-expr
|
||||||
|
t
|
||||||
|
(cond
|
||||||
|
(= h "{") (inc idx)
|
||||||
|
(= h "}") (dec idx)
|
||||||
|
idx)
|
||||||
|
(inc r))))))
|
||||||
|
|
||||||
|
(hidden f-internal)
|
||||||
|
(defndynamic f-internal [s]
|
||||||
|
(let [idx (String.index-of s \{)
|
||||||
|
len (String.length s)]
|
||||||
|
(cond
|
||||||
|
(= idx -1) [(list 'copy s)]
|
||||||
|
|
||||||
|
(= len 1) (macro-error "error in format string: expected expression after last {")
|
||||||
|
|
||||||
|
(= \{ (String.char-at s (inc idx)))
|
||||||
|
(append [(list 'copy (String.prefix s (inc idx)))] (f-internal (String.suffix s (+ 2 idx))))
|
||||||
|
|
||||||
|
(let [ss (String.suffix s (inc idx))
|
||||||
|
endx (f-parse-expr ss 1 0)]
|
||||||
|
(if (= endx -1)
|
||||||
|
(macro-error "error in format string: unclosed open bracket")
|
||||||
|
(append [(list 'copy (String.prefix s idx)) (list 'str (parse (String.prefix ss (dec endx))))]
|
||||||
|
(f-internal (String.suffix ss endx))))))))
|
||||||
|
|
||||||
|
(doc fstr "formats a string. It allows arbitrary expression to be intercalated.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
(def x 1)
|
||||||
|
(def y \"hi\")
|
||||||
|
|
||||||
|
(fstr \"this is x: {x}, and this is the first letter of y: {(head y)}\")
|
||||||
|
``")
|
||||||
|
(defmacro fstr [s]
|
||||||
|
(list 'String.concat (list 'ref (f-internal s))))
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
(list-to-array-internal (cdr xs) (append acc (array (car xs))))))
|
(list-to-array-internal (cdr xs) (append acc (array (car xs))))))
|
||||||
|
|
||||||
(defmodule Dynamic
|
(defmodule Dynamic
|
||||||
|
(defndynamic /= [a b] (not (= a b)))
|
||||||
|
|
||||||
(defndynamic caar [pair] (car (car pair)))
|
(defndynamic caar [pair] (car (car pair)))
|
||||||
(defndynamic cadr [pair] (car (cdr pair)))
|
(defndynamic cadr [pair] (car (cdr pair)))
|
||||||
(defndynamic cdar [pair] (cdr (car pair)))
|
(defndynamic cdar [pair] (cdr (car pair)))
|
||||||
@ -47,6 +49,7 @@
|
|||||||
(defndynamic cdddar [pair] (cdr (cdr (cdr (car pair)))))
|
(defndynamic cdddar [pair] (cdr (cdr (cdr (car pair)))))
|
||||||
(defndynamic cddddr [pair] (cdr (cdr (cdr (cdr pair))))))
|
(defndynamic cddddr [pair] (cdr (cdr (cdr (cdr pair))))))
|
||||||
|
|
||||||
|
|
||||||
(meta-set! doc "doc" "Set documentation for a binding.")
|
(meta-set! doc "doc" "Set documentation for a binding.")
|
||||||
(defmacro doc [name :rest strings]
|
(defmacro doc [name :rest strings]
|
||||||
(let [newline "
|
(let [newline "
|
||||||
|
59
docs/Format_Strings.md
Normal file
59
docs/Format_Strings.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Format Strings
|
||||||
|
|
||||||
|
Carp as to ways to format strings, `fmt` and `fstr`. In this document, we
|
||||||
|
explore both functions in detail.
|
||||||
|
|
||||||
|
## `fmt`
|
||||||
|
|
||||||
|
`fmt` allows for more control than `fstr`, but also requires more knowledge
|
||||||
|
about the data.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(fmt "this is an integer %d and this is a string %s." 1 "hi")
|
||||||
|
```
|
||||||
|
|
||||||
|
It’s works similarly to [`printf`](https://en.wikipedia.org/wiki/Printf_format_string)
|
||||||
|
in C. `fmt` will check that the amount of arguments and format specifiers in
|
||||||
|
the format string match.
|
||||||
|
|
||||||
|
All arguments to `fmt` must implement the `format` interface, which is defined
|
||||||
|
as:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(definterface format (Fn [String a] String)
|
||||||
|
```
|
||||||
|
|
||||||
|
The types are expected to take a format specifier and format according to it.
|
||||||
|
As such, which format specifiers are supported is dependent on the
|
||||||
|
implementation of `format` on that type. Standard library types expose regular
|
||||||
|
format specifiers as in C.
|
||||||
|
|
||||||
|
Please note that, because `format` is often implemented on top of `snprintf`
|
||||||
|
and similar functions, using faulty format specifiers might lead to problems.
|
||||||
|
|
||||||
|
Also, all `fmt` format strings must be literals.
|
||||||
|
|
||||||
|
## `fstr`
|
||||||
|
|
||||||
|
Similarly to `fmt`, `fstr` takes a literal string. It uses a simpler interface
|
||||||
|
than `fmt`, however, in which the expressions are embedded directly into the
|
||||||
|
string and formatted using `str`. As such, the return types of all expressions
|
||||||
|
in a `fstr` must implement the `str` interface.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def x 1)
|
||||||
|
(def y "hi")
|
||||||
|
|
||||||
|
(fstr "this is an integer {x} and this is the first character of a string {(head x)}")
|
||||||
|
```
|
||||||
|
|
||||||
|
Any parseable expression may be embedded in a `fstr`. Expressions are
|
||||||
|
delimited using `{}`. Any lone `}` will be interpreted as a literal, whereas
|
||||||
|
literal `{` need to be escaped as `{{`.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(fstr "{{}") ; => {}
|
||||||
|
```
|
||||||
|
|
||||||
|
While possible, it is discouraged to use complicated or even multiline
|
||||||
|
expressions inside `fstr`.
|
15
src/Emit.hs
15
src/Emit.hs
@ -172,7 +172,7 @@ toC toCMode (Binder meta root) = emitterSrc (execState (visit startingIndent roo
|
|||||||
(Match _) -> dontVisit
|
(Match _) -> dontVisit
|
||||||
With -> dontVisit
|
With -> dontVisit
|
||||||
MetaStub -> dontVisit
|
MetaStub -> dontVisit
|
||||||
visitStr' indent str i =
|
visitStr' indent str i shouldEscape =
|
||||||
-- This will allocate a new string every time the code runs:
|
-- This will allocate a new string every time the code runs:
|
||||||
-- do let var = freshVar i
|
-- do let var = freshVar i
|
||||||
-- appendToSrc (addIndent indent ++ "String " ++ var ++ " = strdup(\"" ++ str ++ "\");\n")
|
-- appendToSrc (addIndent indent ++ "String " ++ var ++ " = strdup(\"" ++ str ++ "\");\n")
|
||||||
@ -181,14 +181,21 @@ toC toCMode (Binder meta root) = emitterSrc (execState (visit startingIndent roo
|
|||||||
do
|
do
|
||||||
let var = freshVar i
|
let var = freshVar i
|
||||||
varRef = freshVar i ++ "_ref"
|
varRef = freshVar i ++ "_ref"
|
||||||
appendToSrc (addIndent indent ++ "static String " ++ var ++ " = \"" ++ escapeString str ++ "\";\n")
|
appendToSrc (addIndent indent ++ "static String " ++ var ++ " = \"" ++ (if shouldEscape then escapeString str else str) ++ "\";\n")
|
||||||
appendToSrc (addIndent indent ++ "String *" ++ varRef ++ " = &" ++ var ++ ";\n")
|
appendToSrc (addIndent indent ++ "String *" ++ varRef ++ " = &" ++ var ++ ";\n")
|
||||||
pure varRef
|
pure varRef
|
||||||
visitString indent (XObj (Str str) (Just i) _) = visitStr' indent str i
|
visitString indent (XObj (Str str) (Just i) _) = visitStr' indent str i True
|
||||||
visitString indent (XObj (Pattern str) (Just i) _) = visitStr' indent str i
|
visitString indent (XObj (Pattern str) (Just i) _) = visitStr' indent str i False
|
||||||
visitString _ _ = error "Not a string."
|
visitString _ _ = error "Not a string."
|
||||||
escaper '\"' acc = "\\\"" ++ acc
|
escaper '\"' acc = "\\\"" ++ acc
|
||||||
|
escaper '\\' acc = "\\\\" ++ acc
|
||||||
escaper '\n' acc = "\\n" ++ acc
|
escaper '\n' acc = "\\n" ++ acc
|
||||||
|
escaper '\a' acc = "\\a" ++ acc
|
||||||
|
escaper '\b' acc = "\\b" ++ acc
|
||||||
|
escaper '\f' acc = "\\f" ++ acc
|
||||||
|
escaper '\r' acc = "\\r" ++ acc
|
||||||
|
escaper '\t' acc = "\\t" ++ acc
|
||||||
|
escaper '\v' acc = "\\v" ++ acc
|
||||||
escaper x acc = x : acc
|
escaper x acc = x : acc
|
||||||
escapeString = foldr escaper ""
|
escapeString = foldr escaper ""
|
||||||
visitSymbol :: Int -> XObj -> State EmitterState String
|
visitSymbol :: Int -> XObj -> State EmitterState String
|
||||||
|
@ -11,7 +11,7 @@ import Data.Bits (shift)
|
|||||||
-- import Text.Parsec.Error (newErrorMessage, Message(..))
|
-- import Text.Parsec.Error (newErrorMessage, Message(..))
|
||||||
-- import Text.Parsec.Pos (newPos)
|
-- import Text.Parsec.Pos (newPos)
|
||||||
|
|
||||||
import Data.Char (ord)
|
import Data.Char (chr, ord)
|
||||||
import Data.List (foldl')
|
import Data.List (foldl')
|
||||||
import Info
|
import Info
|
||||||
import Numeric (readHex)
|
import Numeric (readHex)
|
||||||
@ -277,11 +277,37 @@ pat = do
|
|||||||
escaped :: Parsec.Parsec String ParseState String
|
escaped :: Parsec.Parsec String ParseState String
|
||||||
escaped = do
|
escaped = do
|
||||||
_ <- Parsec.char '\\'
|
_ <- Parsec.char '\\'
|
||||||
c <- Parsec.oneOf ['\\', '\"']
|
c <- Parsec.anyChar
|
||||||
pure $ case c of
|
case c of
|
||||||
'\\' -> "\\\\"
|
'\\' -> pure "\\"
|
||||||
'\"' -> "\""
|
'\"' -> pure "\""
|
||||||
_ -> error "escaped"
|
'\'' -> pure "\'"
|
||||||
|
'a' -> pure "\a"
|
||||||
|
'b' -> pure "\b"
|
||||||
|
'f' -> pure "\f"
|
||||||
|
'n' -> pure "\n"
|
||||||
|
'r' -> pure "\r"
|
||||||
|
't' -> pure "\t"
|
||||||
|
'v' -> pure "\v"
|
||||||
|
'x' -> do
|
||||||
|
hex <- Parsec.many1 (Parsec.oneOf "0123456789abcdefABCDEF")
|
||||||
|
let [(p, "")] = readHex hex
|
||||||
|
return [chr p]
|
||||||
|
'u' -> do
|
||||||
|
hex <- Parsec.count 4 (Parsec.oneOf "0123456789abcdefABCDEF")
|
||||||
|
let [(p, "")] = readHex hex
|
||||||
|
return [chr p]
|
||||||
|
'U' -> do
|
||||||
|
hex <- Parsec.count 8 (Parsec.oneOf "0123456789abcdefABCDEF")
|
||||||
|
let [(p, "")] = readHex hex
|
||||||
|
return [chr p]
|
||||||
|
_ ->
|
||||||
|
if elem c "01234567"
|
||||||
|
then do
|
||||||
|
hex <- Parsec.many1 (Parsec.oneOf "01234567")
|
||||||
|
let [(p, "")] = readHex (c : hex)
|
||||||
|
return [chr p]
|
||||||
|
else pure ('\\' : [c])
|
||||||
|
|
||||||
escapedQuoteChar :: Parsec.Parsec String ParseState Char
|
escapedQuoteChar :: Parsec.Parsec String ParseState Char
|
||||||
escapedQuoteChar = do
|
escapedQuoteChar = do
|
||||||
|
@ -33,4 +33,15 @@
|
|||||||
(assert-equal test
|
(assert-equal test
|
||||||
"10 % 12.0 yay"
|
"10 % 12.0 yay"
|
||||||
&(fmt "%d %% %.1f %s" 10 12.0 "yay")
|
&(fmt "%d %% %.1f %s" 10 12.0 "yay")
|
||||||
"fmt macro works"))
|
"fmt macro works")
|
||||||
|
(assert-equal test
|
||||||
|
"1 [2 3] h"
|
||||||
|
&(let [x 1
|
||||||
|
y [2 3]
|
||||||
|
z "hi"]
|
||||||
|
(fstr "{x} {&y} {(head z)}"))
|
||||||
|
"fstr works")
|
||||||
|
(assert-equal test
|
||||||
|
"{}"
|
||||||
|
&(fstr "{{}")
|
||||||
|
"fstr escaping works"))
|
||||||
|
Loading…
Reference in New Issue
Block a user