Skip to content

Commit

Permalink
update fmt to include some more options
Browse files Browse the repository at this point in the history
  • Loading branch information
thatstoasty committed Apr 22, 2024
1 parent eed59a9 commit 0d0c72e
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 26 deletions.
2 changes: 1 addition & 1 deletion gojo/fmt/__init__.mojo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .fmt import sprintf, printf
from .fmt import sprintf, printf, sprintf_str
128 changes: 103 additions & 25 deletions gojo/fmt/fmt.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,41 @@ Boolean
Integer
%d base 10
%q a single-quoted character literal.
%x base 16, with lower-case letters for a-f
%X base 16, with upper-case letters for A-F
Floating-point and complex constituents:
%f decimal point but no exponent, e.g. 123.456
String and slice of bytes (treated equivalently with these verbs):
%s the uninterpreted bytes of the string or slice
%q a double-quoted string
TODO:
- Add support for more formatting options
- Switch to buffered writing to avoid multiple string concatenations
- Add support for width and precision formatting options
- Handle escaping for String's %q
"""

from utils.variant import Variant
from math import floor
from ..builtins import Byte


alias Args = Variant[String, Int, Float64, Bool]
alias Args = Variant[String, Int, Float64, Bool, List[Byte]]


fn replace_first(s: String, old: String, new: String) -> String:
"""Replace the first occurrence of a substring in a string.
Parameters:
s (str): The original string
old (str): The substring to be replaced
new (str): The new substring
Args:
s: The original string
old: The substring to be replaced
new: The new substring
Returns:
String: The string with the first occurrence of the old substring replaced by the new one.
The string with the first occurrence of the old substring replaced by the new one.
"""
# Find the first occurrence of the old substring
var index = s.find(old)
Expand All @@ -49,49 +55,121 @@ fn replace_first(s: String, old: String, new: String) -> String:
return s


fn format_string(s: String, arg: String) -> String:
return replace_first(s, String("%s"), arg)
fn find_first_verb(s: String, verbs: List[String]) -> String:
"""Find the first occurrence of a verb in a string.
Args:
s: The original string
verbs: The list of verbs to search for.
Returns:
The verb to replace.
"""
var index = -1
var verb: String = ""

for v in verbs:
var i = s.find(v[])
if i != -1 and (index == -1 or i < index):
index = i
verb = v[]

return verb


alias BASE10_TO_BASE16 = List[String]("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f")


fn convert_base10_to_base16(value: Int) -> String:
"""Converts a base 10 number to base 16.
Args:
value: Base 10 number.
Returns:
Base 16 number as a String.
"""

var val: Float64 = 0.0
var result: Float64 = value
var base16: String = ""
while result > 1:
var temp = result / 16
var floor_result = floor(temp)
var remainder = temp - floor_result
result = floor_result
val = 16 * remainder

base16 = BASE10_TO_BASE16[int(val)] + base16

return base16

fn format_integer(s: String, arg: Int) -> String:
return replace_first(s, String("%d"), arg)

fn format_string(format: String, arg: String) -> String:
var verb = find_first_verb(format, List[String]("%s", "%q"))
var arg_to_place = arg
if verb == "%q":
arg_to_place = '"' + arg + '"'

fn format_float(s: String, arg: Float64) -> String:
return replace_first(s, String("%f"), arg)
return replace_first(format, String("%s"), arg)


fn format_boolean(s: String, arg: Bool) -> String:
var value: String = ""
fn format_bytes(format: String, arg: List[Byte]) -> String:
var argument = arg
if argument[-1] != 0:
argument.append(0)

return format_string(format, argument)


fn format_integer(format: String, arg: Int) -> String:
var verb = find_first_verb(format, List[String]("%x", "%X", "%d", "%q"))
var arg_to_place = String(arg)
if verb == "%x":
arg_to_place = String(convert_base10_to_base16(arg)).lower()
elif verb == "%X":
arg_to_place = String(convert_base10_to_base16(arg)).upper()
elif verb == "%q":
arg_to_place = "'" + String(arg) + "'"

return replace_first(format, verb, arg_to_place)


fn format_float(format: String, arg: Float64) -> String:
return replace_first(format, String("%f"), arg)


fn format_boolean(format: String, arg: Bool) -> String:
var value: String = "False"
if arg:
value = "True"
else:
value = "False"

return replace_first(s, String("%t"), value)
return replace_first(format, String("%t"), value)


fn sprintf(formatting: String, *args: Args) raises -> String:
# If the number of arguments does not match the number of format specifiers
alias BadArgCount = "(BAD ARG COUNT)"


fn sprintf(formatting: String, *args: Args) -> String:
var text = formatting
var formatter_count = formatting.count("%")

if formatter_count > len(args):
raise Error("Not enough arguments for format string")
elif formatter_count < len(args):
raise Error("Too many arguments for format string")
if formatter_count != len(args):
return BadArgCount

for i in range(len(args)):
var argument = args[i]
if argument.isa[String]():
text = format_string(text, argument.get[String]()[])
elif argument.isa[List[Byte]]():
text = format_bytes(text, argument.get[List[Byte]]()[])
elif argument.isa[Int]():
text = format_integer(text, argument.get[Int]()[])
elif argument.isa[Float64]():
text = format_float(text, argument.get[Float64]()[])
elif argument.isa[Bool]():
text = format_boolean(text, argument.get[Bool]()[])
else:
raise Error("Unknown for argument #" + String(i))

return text

Expand Down
6 changes: 6 additions & 0 deletions tests/test_fmt.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ fn test_sprintf() raises:
"Hello, world. I am 29 years old. More precisely, I am 29.5 years old. It is True that I like Mojo!",
)

s = sprintf("This is a number: %d. In base 16: %x. In base 16 upper: %X.", 42, 42, 42)
test.assert_equal(s, "This is a number: 42. In base 16: 2a. In base 16 upper: 2A.")

s = sprintf("Hello %s", String("world").as_bytes())
test.assert_equal(s, "Hello world")


fn test_printf() raises:
var test = MojoTest("Testing printf")
Expand Down

0 comments on commit 0d0c72e

Please sign in to comment.