Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parseFloatThousandSep #15421

Closed
wants to merge 86 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
1ecf6ed
Add parseFloatThousandSep
juancarlospaco Sep 28, 2020
817df69
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Sep 29, 2020
0b79ec4
https://github.com/nim-lang/Nim/pull/15421#issuecomment-700791178
juancarlospaco Sep 29, 2020
53c3e18
https://github.com/nim-lang/Nim/pull/15421#issuecomment-700791178
juancarlospaco Sep 29, 2020
b3ada57
https://github.com/nim-lang/Nim/pull/15421#issuecomment-700791178
juancarlospaco Sep 29, 2020
ec57030
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Oct 5, 2020
d63112c
feedback
juancarlospaco Oct 5, 2020
590702a
feedbacks
juancarlospaco Oct 5, 2020
2163a46
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Oct 8, 2020
5b2b630
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Oct 15, 2020
af6821a
https://github.com/nim-lang/Nim/pull/15421#discussion_r505507037
juancarlospaco Oct 15, 2020
d1c768e
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 12, 2020
237aeef
Merge branch 'float-thousands-separators' of https://github.com/juanc…
juancarlospaco Nov 12, 2020
af078ea
https://github.com/nim-lang/Nim/pull/15421#discussion_r522453389
juancarlospaco Nov 12, 2020
923663d
https://github.com/nim-lang/Nim/pull/15421#discussion_r522453304
juancarlospaco Nov 12, 2020
32d68e9
Remove unlikely
juancarlospaco Nov 12, 2020
d2bb63f
Fix Changelog conflicts
juancarlospaco Nov 12, 2020
6547469
https://github.com/nim-lang/Nim/pull/15421#discussion_r522454607
juancarlospaco Nov 12, 2020
bf88002
Delete half runnableExamples
juancarlospaco Nov 13, 2020
2d2f558
Move half runnableExamples to testament
juancarlospaco Nov 13, 2020
a02f7e1
clean out
juancarlospaco Nov 13, 2020
3901c56
https://github.com/nim-lang/Nim/pull/15421#discussion_r522517532
juancarlospaco Nov 13, 2020
4c58bdd
Add raise test
juancarlospaco Nov 13, 2020
e19a15c
See also to func not to module
juancarlospaco Nov 13, 2020
b8310c3
See also to func not to module
juancarlospaco Nov 13, 2020
48828f5
Improve a test
juancarlospaco Nov 13, 2020
a98907b
raise proc
juancarlospaco Nov 13, 2020
e1e43fc
improve the improvements
juancarlospaco Nov 13, 2020
ce8c591
improve the improvements
juancarlospaco Nov 13, 2020
842532d
improve the improvements
juancarlospaco Nov 13, 2020
0d2ac4d
the noinline thingy
juancarlospaco Nov 13, 2020
c754556
char repr
juancarlospaco Nov 13, 2020
c3f56ed
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 13, 2020
c151058
https://github.com/nim-lang/Nim/pull/15421#discussion_r522586680
juancarlospaco Nov 13, 2020
a18df8a
https://github.com/nim-lang/Nim/pull/15421#discussion_r522587122
juancarlospaco Nov 13, 2020
64a306f
https://github.com/nim-lang/Nim/pull/15421#discussion_r522587122
juancarlospaco Nov 13, 2020
f5314a1
https://github.com/nim-lang/Nim/pull/15421#discussion_r522587122
juancarlospaco Nov 13, 2020
fce794c
https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227
juancarlospaco Nov 13, 2020
765b6da
https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227
juancarlospaco Nov 13, 2020
7c6c941
https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227
juancarlospaco Nov 13, 2020
d37375e
https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227
juancarlospaco Nov 13, 2020
f8886ba
Add 1 assert to test
juancarlospaco Nov 13, 2020
744355f
Add 1 assert to test
juancarlospaco Nov 13, 2020
a14b96d
https://github.com/nim-lang/Nim/pull/15421#discussion_r522671941
juancarlospaco Nov 13, 2020
4ec5d07
Add a comment
juancarlospaco Nov 13, 2020
7411586
Remove confusing doc line
juancarlospaco Nov 13, 2020
c8f210d
https://github.com/nim-lang/Nim/pull/15421#discussion_r522678760
juancarlospaco Nov 13, 2020
2ab6f83
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 17, 2020
38b26f1
https://github.com/nim-lang/Nim/pull/15421#discussion_r522841456
juancarlospaco Nov 17, 2020
6a38d57
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 17, 2020
eca6572
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 18, 2020
82a49c3
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 18, 2020
5e55e92
refactor and more tests
juancarlospaco Nov 18, 2020
412c652
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 18, 2020
2a95434
Resync devel
juancarlospaco Nov 18, 2020
e181640
Clean
juancarlospaco Nov 19, 2020
b272708
Clean
juancarlospaco Nov 19, 2020
cd9e3b6
Clean
juancarlospaco Nov 19, 2020
41876f2
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 19, 2020
bbfe1b8
Simplify
juancarlospaco Nov 19, 2020
bf6cf49
Simplify
juancarlospaco Nov 19, 2020
d11359a
Allow {pfLeadingDot,pfTrailingDot}
juancarlospaco Nov 19, 2020
3766512
Allow {pfLeadingDot,pfTrailingDot}
juancarlospaco Nov 19, 2020
4bad51a
https://github.com/nim-lang/Nim/pull/15421#discussion_r526731954
juancarlospaco Nov 19, 2020
18fd3c6
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 20, 2020
4619329
Merge branch 'float-thousands-separators' of https://github.com/juanc…
juancarlospaco Nov 20, 2020
32ae2e5
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 23, 2020
68a8228
https://github.com/nim-lang/Nim/pull/15421/files#r526547810
juancarlospaco Nov 23, 2020
4f3b1ee
peer review feedbacks
juancarlospaco Nov 24, 2020
6dd41c9
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 24, 2020
e90786f
1.0e9
juancarlospaco Nov 24, 2020
87ca6ae
1.0e9
juancarlospaco Nov 24, 2020
bdfc4f5
1.0e9
juancarlospaco Nov 24, 2020
7ef7138
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 24, 2020
2114087
NaN
juancarlospaco Nov 24, 2020
5558fac
ReSync devel
juancarlospaco Nov 24, 2020
cc24825
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 24, 2020
15d832b
is always scientific
juancarlospaco Nov 24, 2020
4769a90
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 24, 2020
12854c1
https://github.com/nim-lang/Nim/pull/15421#discussion_r529793041
juancarlospaco Nov 24, 2020
d747b18
Clean out
juancarlospaco Nov 24, 2020
093b826
moar test
juancarlospaco Nov 24, 2020
c61993e
sep and dot must not be '+', 'e', 'i', 'n', 'f', 'a'
juancarlospaco Nov 24, 2020
8040f52
sep and dot must not be '+', 'e', 'i', 'n', 'f', 'a'
juancarlospaco Nov 24, 2020
92b2ae6
Merge branch 'devel' of https://github.com/nim-lang/Nim into float-th…
juancarlospaco Nov 29, 2020
fcc5c35
We need doAssert that takes varargs for tests
juancarlospaco Nov 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

- `doAssertRaises` now correctly handles foreign exceptions.

- Add `strmisc.parseFloatThousandSep` designed to parse floats as found in the wild formatted for humans.
- Added `asyncdispatch.activeDescriptors` that returns the number of currently
active async event handles/file descriptors.

Expand Down
132 changes: 132 additions & 0 deletions lib/pure/strmisc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
## used in comparison to `strutils <strutils.html>`_.

import strutils
import std/private/since

proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect.} =
## Expand tab characters in `s` replacing them by spaces.
Expand Down Expand Up @@ -84,3 +85,134 @@ proc rpartition*(s: string, sep: string): (string, string, string)
doAssert rpartition("foofoobar", "bar") == ("foofoo", "bar", "")

return partition(s, sep, right = true)


since (1, 5):
type ParseFloatOptions* = enum ## Options for `parseFloatThousandSep`.
pfLeadingDot, ## Allow leading dot, like ".9" and similar.
pfTrailingDot, ## Allow trailing dot, like "9." and similar.
pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99".
pfDotOptional ## Allow "9", "-0", integer literals, etc.
pfNanInf ## Allow "NaN", "Inf", "-Inf", etc.

func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {};
sep = ','; decimalDot = '.'): float =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be discussed in future work, but just curious if there's a reverse proc for this?
I just found about insertSep, maybe they should cross-reference each other?
I'm not sure how robust insertSep is though; there's also formatFloat and formatEng, maybe they should be extended to support formatting with thousand separators

## Convenience func for `parseFloat` which allows for thousand separators,
## this is designed to parse floats as found in the wild formatted for humans.
##
## Fine grained flexibility and strictness is up to the user,
## you can set the `options` using `ParseFloatOptions` enum.
##
## `parseFloatThousandSep` "prepares" `str` and then calls `parseFloat`,
## consequently `parseFloatThousandSep` by design is slower than `parseFloat`.
##
## The following assumptions and requirements must be met:
## - `str` must not be empty string.
## - `str` must be stripped of trailing and leading whitespaces.
## - `sep` and `decimalDot` must be different.
## - `sep` must not be in `{'-', '+', 'e', 'i', 'n', 'f', 'a', '\n'}`.
## - `decimalDot` must not be in `{'-', '+', 'e', 'i', 'n', 'f', 'a', ' ', '\t', '\v', '\c', '\n', '\f'}`.
##
## See also:
## * `parseFloat <strutils.html#parseFloat,string>`_
runnableExamples:
doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0
doAssert parseFloatThousandSep("1,222.0001") == 1222.0001
doAssert parseFloatThousandSep("10.000,0", {}, '.', ',') == 10000.0
doAssert parseFloatThousandSep("1'000'000,000", {}, '\'', ',') == 1000000.0
doAssert parseFloatThousandSep("1000000", {pfDotOptional}) == 1000000.0
doAssert parseFloatThousandSep("-1,000", {pfDotOptional}) == -1000.0
## You can omit `sep`, but then all subsequent `sep` to the left must also be omitted:
doAssert parseFloatThousandSep("1000,000", {pfDotOptional}) == 1000000.0
## Examples using different ParseFloatOptions:
doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1
doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0
doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0
juancarlospaco marked this conversation as resolved.
Show resolved Hide resolved
doAssert parseFloatThousandSep("10,0.0,0,0", {pfSepAnywhere}) == 100.0
doAssert parseFloatThousandSep("01.00") == 1.0
doAssert parseFloatThousandSep("1,000.000e-9") == 1e-06

assert decimalDot notin {'-', '+', 'e', 'i', 'n', 'f', 'a', ' ', '\t', '\v', '\c', '\n', '\f'}
assert sep notin {'-', '+', 'e', 'i', 'n', 'f', 'a', '\n'}
assert sep != decimalDot

proc parseFloatThousandSepRaise(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} =
raise newException(ValueError,
"Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" %
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

      raise newException(ValueError,
        "Invalid float containing thousand separators, invalid char $1 at index $2 for input '$3'" % [$c, $i, s.join])

so that it shows as

'1,0000'

instead of

['1', ',', '0', '0', '0', '0'] 

[$c, $i, $s])

# Fail fast, before looping.
let strLen = str.len
if strLen == 0: # Empty string.
parseFloatThousandSepRaise(0, ' ', "empty string")
if str[0] == sep: # ",1"
parseFloatThousandSepRaise(0, sep, str)
if pfLeadingDot notin options and str[0] == decimalDot: # ".1"
parseFloatThousandSepRaise(0, decimalDot, str)
if str[^1] == sep: # "1,"
parseFloatThousandSepRaise(strLen, sep, str)
if pfTrailingDot notin options and str[^1] == decimalDot: # "1."
parseFloatThousandSepRaise(strLen, decimalDot, str)
if pfSepAnywhere notin options and (str.len <= 4 and sep in str):
parseFloatThousandSepRaise(0, sep, str) # "1,1"

if (strLen == 3 or strLen == 4) and (
(str[0] in {'i', 'I'} and str[1] in {'n', 'N'} and str[2] in {'f', 'F'}) or
Copy link
Member

@timotheecour timotheecour Dec 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cna you simplify this logic? it's buggy for 2 reasons:

nim> echo parseFloatThousandSep("infx", {pfNanInf})
Error: unhandled exception: invalid float: infx [ValueError]
nim> echo parseFloatThousandSep("infxx", {pfNanInf})
Error: unhandled exception: Invalid float containing thousand separators, invalid char , at index 0 for input 'infxx' [ValueError]

(str[0] in {'n', 'N'} and str[1] in {'a', 'A'} and str[2] in {'n', 'N'}) or
(str[0] in {'+', '-'} and str[1] in {'i', 'I'} and str[2] in {'n', 'N'} and str[3] in {'f', 'F'}) or
(str[0] in {'+', '-'} and str[1] in {'n', 'N'} and str[2] in {'a', 'A'} and str[3] in {'n', 'N'})):
if pfNanInf notin options:
parseFloatThousandSepRaise(0, sep, str)
else:
return parseFloat(str.join) # Allow NaN, Inf, -Inf, +Inf

var
s = newStringOfCap(strLen)
successive: int
afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative, hasAnyDot, isScientific: bool

for idx, c in str:
if c in '0' .. '9': # Digits
if hasAnySep and not afterDot and successive > 2:
parseFloatThousandSepRaise(idx, c, str)
else:
s.add c
lastWasSep = false
lastWasDot = false
inc successive
elif c == sep: # Thousands separator, this is NOT the dot
if pfSepAnywhere notin options and (lastWasSep or afterDot) or
(isNegative and idx == 1 or idx == 0) or isScientific:
parseFloatThousandSepRaise(idx, c, str)
else:
lastWasSep = true # Do NOT add the Thousands separator here.
hasAnySep = true
successive = 0
elif c == decimalDot: # This is the dot
if (not afterDot and not hasAnyDot and not lastWasDot) and
(pfLeadingDot notin options and (isNegative and idx == 1 or idx == 0)) or
(hasAnySep and pfSepAnywhere notin options and successive != 3): # Disallow .1
parseFloatThousandSepRaise(idx, c, str)
else:
s.add '.' # Replace decimalDot to '.' so parseFloat can take it.
successive = 0
lastWasDot = true
afterDot = true
hasAnyDot = true
elif c == '-': # Allow negative float
if isNegative or idx != 0 and not isScientific: # Disallow ---1.0
parseFloatThousandSepRaise(idx, c, str) # Allow 1.0e-9
else:
s.add '-'
if idx == 0: # Allow 1.0e-9
isNegative = true
elif c in {'e', 'E'}: # Allow scientific notation
if isScientific:
parseFloatThousandSepRaise(idx, c, str)
else:
s.add 'e'
isScientific = true

if pfDotOptional notin options and not hasAnyDot:
parseFloatThousandSepRaise(0, sep, str)
result = parseFloat(s)
3 changes: 3 additions & 0 deletions lib/pure/strutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,9 @@ proc parseFloat*(s: string): float {.noSideEffect,
##
## If `s` is not a valid floating point number, `ValueError` is raised.
##``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison).
##
## See also:
## * `parseFloatThousandSep <strmisc.html#parseFloatThousandSep,openArray[char],char,char>`_
runnableExamples:
doAssert parseFloat("3.14") == 3.14
doAssert parseFloat("inf") == 1.0/0
Expand Down
59 changes: 59 additions & 0 deletions tests/stdlib/tstrmisc.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import strmisc, math
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move the content of tests/stdlib/tstrmiscs.nim into here (tstrmiscs.nim was badly named)



func main() =
doAssert parseFloatThousandSep("0.0") == 0.0
juancarlospaco marked this conversation as resolved.
Show resolved Hide resolved
doAssert parseFloatThousandSep("1.0") == 1.0
doAssert parseFloatThousandSep("-0.0") == -0.0
doAssert parseFloatThousandSep("-1.0") == -1.0
doAssert parseFloatThousandSep("1.000") == 1.0
doAssert parseFloatThousandSep("1.000") == 1.0
doAssert parseFloatThousandSep("-1.000") == -1.0
doAssert parseFloatThousandSep("-1,222.0001") == -1222.0001
doAssert parseFloatThousandSep("3.141592653589793") == 3.141592653589793
doAssert parseFloatThousandSep("6.283185307179586") == 6.283185307179586
doAssert parseFloatThousandSep("2.718281828459045") == 2.718281828459045

doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional})
doAssertRaises(ValueError): discard parseFloatThousandSep(".1.", {pfLeadingDot,pfTrailingDot})
doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfDotOptional})
doAssertRaises(ValueError): discard parseFloatThousandSep("aNa", {pfNanInf})
doAssertRaises(ValueError): discard parseFloatThousandSep("fnI", {pfNanInf})
doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000,000,E,+,9,0", {pfSepAnywhere})
for s in ["1,11", "1,1", "1,0000.000", "--", "..", "1,,000", "1..000",
"1,000000", ",1", "1,", "1.", ".1", "10,00.0", "1,000.000ee9", "1e02.2",
"1.0e--9", "Inf", "-Inf", "+Inf", "NaN"]:
doAssertRaises(ValueError): discard parseFloatThousandSep(s)

doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0
doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0
doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0
doAssert parseFloatThousandSep("1,111", {pfDotOptional}) == 1111.0
doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1
doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0
doAssert parseFloatThousandSep(".1", {pfLeadingDot,pfTrailingDot}) == 0.1
doAssert parseFloatThousandSep("1.", {pfLeadingDot,pfTrailingDot}) == 1.0
doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0
doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0
doAssert parseFloatThousandSep(".10", {pfLeadingDot}) == 0.1
doAssert parseFloatThousandSep("10.", {pfTrailingDot}) == 10.0
doAssert parseFloatThousandSep("10", {pfDotOptional, pfSepAnywhere}) == 10.0
doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0
doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1
doAssert parseFloatThousandSep("1.0e9") == 1000000000.0
doAssert parseFloatThousandSep("1.0e-9") == 1e-09
doAssert parseFloatThousandSep("1,000.000e9") == 1000000000000.0
doAssert parseFloatThousandSep("1e9", {pfDotOptional}) == 1000000000.0
doAssert parseFloatThousandSep("1.0E9") == 1000000000.0
doAssert parseFloatThousandSep("1.0E-9") == 1e-09
doAssert parseFloatThousandSep("Inf", {pfNanInf}) == Inf
doAssert parseFloatThousandSep("-Inf", {pfNanInf}) == -Inf
doAssert parseFloatThousandSep("+Inf", {pfNanInf}) == +Inf
juancarlospaco marked this conversation as resolved.
Show resolved Hide resolved
doAssert parseFloatThousandSep("1000.000000E+90") == 1e93
doAssert parseFloatThousandSep("-10 000 000 000.0001", sep=' ') == -10000000000.0001
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseFloatThousandSep("1e1") raises but should be accepted even without {pfDotOptional} because:

  • a user may not want to set {pfDotOptional} (too loose, eg would allow integers to be parsed as float)
  • nim: strutils.parseFloat accepts it
  • D: accepts it too (rdmd --eval 'writeln("1e1".to!double);')
  • python3: accepts it (float("1e1"))

doAssert parseFloatThousandSep("-10 000 000 000,0001", sep=' ', decimalDot = ',') == -10000000000.0001
doAssert classify(parseFloatThousandSep("NaN", {pfNanInf})) == fcNan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug, this shouldn't be accepted:

nim> echo parseFloatThousandSep("inf.0", {pfNanInf})
0.0


juancarlospaco marked this conversation as resolved.
Show resolved Hide resolved

juancarlospaco marked this conversation as resolved.
Show resolved Hide resolved
main()
static: main()