-
Notifications
You must be signed in to change notification settings - Fork 235
Expand file tree
/
Copy pathfstring.star
More file actions
133 lines (97 loc) · 4.27 KB
/
fstring.star
File metadata and controls
133 lines (97 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# Tests of Starlark 'fstring'
# option:set
# Tests of Starlark f-string literals
# syntax identical to Python 3.6+ (no '=', no '\{', no nested '{ }')
load("assert.star", "assert")
# --- basic interpolation ----------------------------------------------------
name = "Starlark"
version = 1
assert.eq(f"hello {name}", "hello Starlark")
assert.eq(f"{{}} {name}", "{} Starlark")
assert.eq(f"{{{{}}}} {name}", "{{}} Starlark")
assert.eq(f"{name} {version}", "Starlark 1")
assert.eq(f"{{ literal }}", "{ literal }") # doubled braces → literal
assert.eq(f"start{ {"x":3} }end", "start{\"x\": 3}end") # dict
# --- conversion flags -------------------------------------------------------
# todo: future plans
# pi = 3.14
# assert.eq(f"{pi!s}", "3.14") # str()
# assert.eq(f"{pi!r}", "3.14") # repr() (same here because str/repr identical)
# big = 1000000
# assert.eq(f"{big:,}", "1,000,000") # format-spec with ','
# --- field names -----------------------------------------------------------
d = {"x": 10, "y": 20}
assert.eq(f"{d['x']}", "10")
assert.eq(f"{d['y']}", "20")
# positional and keyword in .format() style already tested elsewhere;
# f-strings use **inline** expressions, so we just check they evaluate.
tpl = (4, 5)
assert.eq(f"{tpl[0]} and {tpl[1]}", "4 and 5")
# --- padding / alignment / precision ---------------------------------------
#todo: future plans
# n = 42
# assert.eq(f"{n:>5}", " 42")
# assert.eq(f"{n:<5}", "42 ")
# assert.eq(f"{n:^5}", " 42 ")
# assert.eq(f"{n:05}", "00042")
# f = 1.234567
# assert.eq(f"{f:.2f}", "1.23")
# --- escaping --------------------------------------------------------------
assert.eq(f"backslash \\ still one", "backslash \\ still one")
assert.eq(f"quotes ' and \" kept", 'quotes \' and " kept')
# --- empty f-string --------------------------------------------------------
assert.eq(f"", "")
# --- nested quotes (no problem) --------------------------------------------
assert.eq(f'He said "Hello {name}"', 'He said "Hello Starlark"')
assert.eq(f"it's {version} o'clock", "it's 1 o'clock")
# --- multiline f-string ----------------------------------------------------
msg = f"""
hello {name}
version {version}
""".strip()
assert.true(msg.startswith("hello Starlark"))
assert.true(msg.endswith("version 1"))
# --- unicode ---------------------------------------------------------------
α = 2
assert.eq(f"α = {α}", "α = 2")
# --- errors that must be caught at compile-time ----------------------------
#todo? more sound errors, now raises with text: `expect "}}" or "{expression}", got single"}"` on almost all errors
# (Un-comment each block to verify the parser rejects it.)
# 1. single '}' without '{'
# assert.fails(lambda: f"oops}", "single '}' in format")
# 2. unmatched '{' #now says "unexpected new line in string". is it ok?
# assert.fails(lambda: f"oops{", "unmatched '{' in format")
# 3. invalid expression inside braces # now fails with "got , want primary expression"
# assert.fails(lambda: f"{1+}", "invalid syntax")
# 4. unknown conversion #future plans?
# assert.fails(lambda: f"{pi!z}", "unknown conversion")
# 5. # now fails with "got , want primary expression"
# assert.fails(lambda: f"nothing{}", "nothing{}") # todo: assert fails
# --- runtime errors --------------------------------------------------------
# expression raises → propagates
def raise_error():
f"{1/0}"
assert.fails(raise_error, "division by zero")
# --- interaction with other string features -------------------------------
# f-string is still a plain string afterwards
s = f"{name}"
assert.eq(s.upper(), "STARLARK")
assert.eq(s * 2, "StarlarkStarlark")
assert.eq(len(s), 8)
# concatenation
assert.eq(f"A" + f"{name}" + f"B", "AStarlarkB")
# raw *and* f is illegal in Python; Starlark should follow
# (un-comment to check)
# assert.fails(lambda: rf"{name}", "cannot use both raw and f-string") #todo?
# --- edge cases ------------------------------------------------------------
# only doubled braces
assert.eq(f"{{}}", "{}")
# mixed
#todo: fix this or just look into this
# assert.eq(f"{{ {name} }}", "{ Starlark }")
# zero-width joiner emoji (4-byte UTF-8)
emoji = "😿"
assert.eq(f"{emoji}", "😿")
# assert.eq(f"{emoji!r}", '"😿"') # todo: future plans?
# ---------------------------------------------------------------------------
# end of f-string tests