mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Add to_str to other number types
This commit is contained in:
parent
53d31c8003
commit
c92babb47d
5 changed files with 291 additions and 14 deletions
|
|
@ -167,12 +167,44 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
|
|||
try low_level_map.put(ident, .num_is_ne);
|
||||
}
|
||||
|
||||
// Dec to_str operation
|
||||
// Note: Dec is nested under Num in Builtin.roc, so the canonical identifier is
|
||||
// Numeric to_str operations (all numeric types)
|
||||
// Note: Types like Dec are nested under Num in Builtin.roc, so the canonical identifier is
|
||||
// "Builtin.Num.Dec.to_str". But Dec is auto-imported as "Dec", so user code
|
||||
// calling Dec.to_str looks up "Builtin.Dec.to_str". We need the canonical name here.
|
||||
if (env.common.findIdent("Builtin.Num.Dec.to_str")) |ident| {
|
||||
try low_level_map.put(ident, .dec_to_str);
|
||||
for (numeric_types) |num_type| {
|
||||
var buf: [256]u8 = undefined;
|
||||
const to_str_name = try std.fmt.bufPrint(&buf, "Builtin.Num.{s}.to_str", .{num_type});
|
||||
if (env.common.findIdent(to_str_name)) |ident| {
|
||||
const low_level_op: CIR.Expr.LowLevel = if (std.mem.eql(u8, num_type, "U8"))
|
||||
.u8_to_str
|
||||
else if (std.mem.eql(u8, num_type, "I8"))
|
||||
.i8_to_str
|
||||
else if (std.mem.eql(u8, num_type, "U16"))
|
||||
.u16_to_str
|
||||
else if (std.mem.eql(u8, num_type, "I16"))
|
||||
.i16_to_str
|
||||
else if (std.mem.eql(u8, num_type, "U32"))
|
||||
.u32_to_str
|
||||
else if (std.mem.eql(u8, num_type, "I32"))
|
||||
.i32_to_str
|
||||
else if (std.mem.eql(u8, num_type, "U64"))
|
||||
.u64_to_str
|
||||
else if (std.mem.eql(u8, num_type, "I64"))
|
||||
.i64_to_str
|
||||
else if (std.mem.eql(u8, num_type, "U128"))
|
||||
.u128_to_str
|
||||
else if (std.mem.eql(u8, num_type, "I128"))
|
||||
.i128_to_str
|
||||
else if (std.mem.eql(u8, num_type, "Dec"))
|
||||
.dec_to_str
|
||||
else if (std.mem.eql(u8, num_type, "F32"))
|
||||
.f32_to_str
|
||||
else if (std.mem.eql(u8, num_type, "F64"))
|
||||
.f64_to_str
|
||||
else
|
||||
continue;
|
||||
try low_level_map.put(ident, low_level_op);
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric comparison operations (all numeric types)
|
||||
|
|
@ -309,7 +341,7 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
|
|||
// Create parameter patterns for the lambda
|
||||
// Binary operations need 2 parameters, unary operations need 1
|
||||
const num_params: u32 = switch (low_level_op) {
|
||||
.num_negate, .num_is_zero, .num_is_negative, .num_is_positive, .num_from_numeral, .num_from_int_digits, .dec_to_str => 1,
|
||||
.num_negate, .num_is_zero, .num_is_negative, .num_is_positive, .num_from_numeral, .num_from_int_digits, .u8_to_str, .i8_to_str, .u16_to_str, .i16_to_str, .u32_to_str, .i32_to_str, .u64_to_str, .i64_to_str, .u128_to_str, .i128_to_str, .dec_to_str, .f32_to_str, .f64_to_str => 1,
|
||||
else => 2, // Most numeric operations are binary
|
||||
};
|
||||
|
||||
|
|
@ -357,15 +389,22 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
|
|||
}
|
||||
}
|
||||
|
||||
// Expose Dec.to_str under the alias "Builtin.Dec.to_str" for user code lookups.
|
||||
// The canonical name is "Builtin.Num.Dec.to_str" (since Dec is nested under Num),
|
||||
// Expose to_str under aliases like "Builtin.Dec.to_str" for user code lookups.
|
||||
// The canonical names are like "Builtin.Num.Dec.to_str" (since numeric types are nested under Num),
|
||||
// but user code calling Dec.to_str will look for "Builtin.Dec.to_str".
|
||||
if (env.common.findIdent("Builtin.Num.Dec.to_str")) |canonical_ident| {
|
||||
if (env.getExposedNodeIndexById(canonical_ident)) |node_idx| {
|
||||
// Insert the alias identifier
|
||||
const alias_ident = try env.common.insertIdent(gpa, base.Ident.for_text("Builtin.Dec.to_str"));
|
||||
// Expose the same node under the alias
|
||||
try env.common.setNodeIndexById(gpa, alias_ident, node_idx);
|
||||
for (numeric_types) |num_type| {
|
||||
var canonical_buf: [256]u8 = undefined;
|
||||
var alias_buf: [256]u8 = undefined;
|
||||
const canonical_name = try std.fmt.bufPrint(&canonical_buf, "Builtin.Num.{s}.to_str", .{num_type});
|
||||
const alias_name = try std.fmt.bufPrint(&alias_buf, "Builtin.{s}.to_str", .{num_type});
|
||||
|
||||
if (env.common.findIdent(canonical_name)) |canonical_ident| {
|
||||
if (env.getExposedNodeIndexById(canonical_ident)) |node_idx| {
|
||||
// Insert the alias identifier
|
||||
const alias_ident = try env.common.insertIdent(gpa, base.Ident.for_text(alias_name));
|
||||
// Expose the same node under the alias
|
||||
try env.common.setNodeIndexById(gpa, alias_ident, node_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
U8 :: [].{
|
||||
to_str : U8 -> Str
|
||||
is_zero : U8 -> Bool
|
||||
is_eq : U8, U8 -> Bool
|
||||
is_gt : U8, U8 -> Bool
|
||||
|
|
@ -158,6 +159,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
I8 :: [].{
|
||||
to_str : I8 -> Str
|
||||
is_zero : I8 -> Bool
|
||||
is_negative : I8 -> Bool
|
||||
is_positive : I8 -> Bool
|
||||
|
|
@ -180,6 +182,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
U16 :: [].{
|
||||
to_str : U16 -> Str
|
||||
is_zero : U16 -> Bool
|
||||
is_eq : U16, U16 -> Bool
|
||||
is_gt : U16, U16 -> Bool
|
||||
|
|
@ -199,6 +202,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
I16 :: [].{
|
||||
to_str : I16 -> Str
|
||||
is_zero : I16 -> Bool
|
||||
is_negative : I16 -> Bool
|
||||
is_positive : I16 -> Bool
|
||||
|
|
@ -221,6 +225,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
U32 :: [].{
|
||||
to_str : U32 -> Str
|
||||
is_zero : U32 -> Bool
|
||||
is_eq : U32, U32 -> Bool
|
||||
is_gt : U32, U32 -> Bool
|
||||
|
|
@ -240,6 +245,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
I32 :: [].{
|
||||
to_str : I32 -> Str
|
||||
is_zero : I32 -> Bool
|
||||
is_negative : I32 -> Bool
|
||||
is_positive : I32 -> Bool
|
||||
|
|
@ -262,6 +268,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
U64 :: [].{
|
||||
to_str : U64 -> Str
|
||||
is_zero : U64 -> Bool
|
||||
is_eq : U64, U64 -> Bool
|
||||
is_gt : U64, U64 -> Bool
|
||||
|
|
@ -281,6 +288,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
I64 :: [].{
|
||||
to_str : I64 -> Str
|
||||
is_zero : I64 -> Bool
|
||||
is_negative : I64 -> Bool
|
||||
is_positive : I64 -> Bool
|
||||
|
|
@ -303,6 +311,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
U128 :: [].{
|
||||
to_str : U128 -> Str
|
||||
is_zero : U128 -> Bool
|
||||
is_eq : U128, U128 -> Bool
|
||||
is_gt : U128, U128 -> Bool
|
||||
|
|
@ -322,6 +331,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
I128 :: [].{
|
||||
to_str : I128 -> Str
|
||||
is_zero : I128 -> Bool
|
||||
is_negative : I128 -> Bool
|
||||
is_positive : I128 -> Bool
|
||||
|
|
@ -369,6 +379,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
F32 :: [].{
|
||||
to_str : F32 -> Str
|
||||
is_zero : F32 -> Bool
|
||||
is_negative : F32 -> Bool
|
||||
is_positive : F32 -> Bool
|
||||
|
|
@ -391,6 +402,7 @@ Builtin :: [].{
|
|||
}
|
||||
|
||||
F64 :: [].{
|
||||
to_str : F64 -> Str
|
||||
is_zero : F64 -> Bool
|
||||
is_negative : F64 -> Bool
|
||||
is_positive : F64 -> Bool
|
||||
|
|
|
|||
|
|
@ -402,8 +402,20 @@ pub const Expr = union(enum) {
|
|||
// String operations
|
||||
str_is_empty,
|
||||
|
||||
// Dec operations
|
||||
// Numeric to_str operations
|
||||
u8_to_str,
|
||||
i8_to_str,
|
||||
u16_to_str,
|
||||
i16_to_str,
|
||||
u32_to_str,
|
||||
i32_to_str,
|
||||
u64_to_str,
|
||||
i64_to_str,
|
||||
u128_to_str,
|
||||
i128_to_str,
|
||||
dec_to_str,
|
||||
f32_to_str,
|
||||
f64_to_str,
|
||||
|
||||
// List operations
|
||||
list_len,
|
||||
|
|
|
|||
|
|
@ -4034,6 +4034,18 @@ pub const Interpreter = struct {
|
|||
roc_str_ptr.* = result_str;
|
||||
return value;
|
||||
},
|
||||
.u8_to_str => return self.intToStr(u8, args, roc_ops),
|
||||
.i8_to_str => return self.intToStr(i8, args, roc_ops),
|
||||
.u16_to_str => return self.intToStr(u16, args, roc_ops),
|
||||
.i16_to_str => return self.intToStr(i16, args, roc_ops),
|
||||
.u32_to_str => return self.intToStr(u32, args, roc_ops),
|
||||
.i32_to_str => return self.intToStr(i32, args, roc_ops),
|
||||
.u64_to_str => return self.intToStr(u64, args, roc_ops),
|
||||
.i64_to_str => return self.intToStr(i64, args, roc_ops),
|
||||
.u128_to_str => return self.intToStr(u128, args, roc_ops),
|
||||
.i128_to_str => return self.intToStr(i128, args, roc_ops),
|
||||
.f32_to_str => return self.floatToStr(f32, args, roc_ops),
|
||||
.f64_to_str => return self.floatToStr(f64, args, roc_ops),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4049,6 +4061,48 @@ pub const Interpreter = struct {
|
|||
return bool_value;
|
||||
}
|
||||
|
||||
/// Helper for integer to_str operations
|
||||
fn intToStr(self: *Interpreter, comptime T: type, args: []const StackValue, roc_ops: *RocOps) !StackValue {
|
||||
std.debug.assert(args.len == 1);
|
||||
const int_arg = args[0];
|
||||
if (int_arg.ptr == null) {
|
||||
self.triggerCrash("int_to_str: null argument", false, roc_ops);
|
||||
return error.Crash;
|
||||
}
|
||||
|
||||
const int_value: T = @as(*const T, @ptrCast(@alignCast(int_arg.ptr.?))).*;
|
||||
|
||||
// Use std.fmt to format the integer
|
||||
var buf: [40]u8 = undefined; // 40 is enough for i128
|
||||
const result = std.fmt.bufPrint(&buf, "{}", .{int_value}) catch unreachable;
|
||||
|
||||
const value = try self.pushStr("");
|
||||
const roc_str_ptr: *RocStr = @ptrCast(@alignCast(value.ptr.?));
|
||||
roc_str_ptr.* = RocStr.init(&buf, result.len, roc_ops);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Helper for float to_str operations
|
||||
fn floatToStr(self: *Interpreter, comptime T: type, args: []const StackValue, roc_ops: *RocOps) !StackValue {
|
||||
std.debug.assert(args.len == 1);
|
||||
const float_arg = args[0];
|
||||
if (float_arg.ptr == null) {
|
||||
self.triggerCrash("float_to_str: null argument", false, roc_ops);
|
||||
return error.Crash;
|
||||
}
|
||||
|
||||
const float_value: T = @as(*const T, @ptrCast(@alignCast(float_arg.ptr.?))).*;
|
||||
|
||||
// Use std.fmt to format the float
|
||||
var buf: [400]u8 = undefined;
|
||||
const result = std.fmt.bufPrint(&buf, "{d}", .{float_value}) catch unreachable;
|
||||
|
||||
const value = try self.pushStr("");
|
||||
const roc_str_ptr: *RocStr = @ptrCast(@alignCast(value.ptr.?));
|
||||
roc_str_ptr.* = RocStr.init(&buf, result.len, roc_ops);
|
||||
return value;
|
||||
}
|
||||
|
||||
fn triggerCrash(self: *Interpreter, message: []const u8, owned: bool, roc_ops: *RocOps) void {
|
||||
defer if (owned) self.allocator.free(@constCast(message));
|
||||
roc_ops.crash(message);
|
||||
|
|
|
|||
|
|
@ -327,3 +327,163 @@ test "e_low_level_lambda - Dec.to_str with zero" {
|
|||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"0.0\"", value);
|
||||
}
|
||||
|
||||
// Integer to_str tests
|
||||
|
||||
test "e_low_level_lambda - U8.to_str" {
|
||||
const src =
|
||||
\\a : U8
|
||||
\\a = 42u8
|
||||
\\x = U8.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"42\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - I8.to_str with negative" {
|
||||
const src =
|
||||
\\a : I8
|
||||
\\a = -42i8
|
||||
\\x = I8.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"-42\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - U16.to_str" {
|
||||
const src =
|
||||
\\a : U16
|
||||
\\a = 1000u16
|
||||
\\x = U16.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"1000\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - I16.to_str with negative" {
|
||||
const src =
|
||||
\\a : I16
|
||||
\\a = -500i16
|
||||
\\x = I16.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"-500\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - U32.to_str" {
|
||||
const src =
|
||||
\\a : U32
|
||||
\\a = 100000u32
|
||||
\\x = U32.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"100000\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - I32.to_str with negative" {
|
||||
const src =
|
||||
\\a : I32
|
||||
\\a = -12345i32
|
||||
\\x = I32.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"-12345\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - U64.to_str" {
|
||||
const src =
|
||||
\\a : U64
|
||||
\\a = 9876543210u64
|
||||
\\x = U64.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"9876543210\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - I64.to_str with negative" {
|
||||
const src =
|
||||
\\a : I64
|
||||
\\a = -9876543210i64
|
||||
\\x = I64.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"-9876543210\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - U128.to_str" {
|
||||
const src =
|
||||
\\a : U128
|
||||
\\a = 12345678901234567890u128
|
||||
\\x = U128.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"12345678901234567890\"", value);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - I128.to_str with negative" {
|
||||
const src =
|
||||
\\a : I128
|
||||
\\a = -12345678901234567890i128
|
||||
\\x = I128.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expectEqualStrings("\"-12345678901234567890\"", value);
|
||||
}
|
||||
|
||||
// Float to_str tests
|
||||
|
||||
test "e_low_level_lambda - F32.to_str" {
|
||||
const src =
|
||||
\\a : F32
|
||||
\\a = 3.14f32
|
||||
\\x = F32.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
// F32 has limited precision, so we just check it starts correctly
|
||||
try testing.expect(std.mem.startsWith(u8, value, "\"3.14"));
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - F64.to_str" {
|
||||
const src =
|
||||
\\a : F64
|
||||
\\a = 3.14159265359f64
|
||||
\\x = F64.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
// F64 has more precision than F32
|
||||
try testing.expect(std.mem.startsWith(u8, value, "\"3.141592"));
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - F32.to_str with negative" {
|
||||
const src =
|
||||
\\a : F32
|
||||
\\a = -2.5f32
|
||||
\\x = F32.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expect(std.mem.startsWith(u8, value, "\"-2.5"));
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - F64.to_str with negative" {
|
||||
const src =
|
||||
\\a : F64
|
||||
\\a = -123.456f64
|
||||
\\x = F64.to_str(a)
|
||||
;
|
||||
const value = try evalModuleAndGetString(src, 1, test_allocator);
|
||||
defer test_allocator.free(value);
|
||||
try testing.expect(std.mem.startsWith(u8, value, "\"-123.456"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue