Bastic value type casting. (0.12.0)
This commit is contained in:
parent
b6cff0c865
commit
c56a130896
8 changed files with 161 additions and 115 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fun VERSION 0.11.1 LANGUAGES C)
|
||||
project(fun VERSION 0.12.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
54
examples/cast_demo.fun
Executable file
54
examples/cast_demo.fun
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen
|
||||
* Licensed under the terms of the ISC license.
|
||||
*
|
||||
* Added: 2025-09-29
|
||||
*/
|
||||
|
||||
print("=== CAST demo ===")
|
||||
|
||||
// Number: parse decimal strings; invalid -> 0
|
||||
print(cast("123", "Number")) // -> 123
|
||||
print(cast("12x", "Number")) // -> 0
|
||||
print(cast(42, "Number")) // -> 42
|
||||
|
||||
// String: stringify any value
|
||||
print(cast(100, "String")) // -> "100"
|
||||
print(typeof(cast(100, "String"))) // -> "String"
|
||||
|
||||
// Boolean: 0 -> 0, non-zero -> 1
|
||||
print(cast(0, "Boolean")) // -> 0
|
||||
print(cast(42, "Boolean")) // -> 1
|
||||
|
||||
// Array: arrays stay arrays; others get wrapped as single-element arrays
|
||||
a = cast(42, "Array")
|
||||
print(typeof(a)) // -> "Array"
|
||||
print(len(a)) // -> 1
|
||||
print(a[0]) // -> 42
|
||||
|
||||
// Map: maps stay maps; others become empty maps
|
||||
m = cast(42, "Map")
|
||||
print(typeof(m)) // -> "Map"
|
||||
// Expect zero keys for a fresh empty map
|
||||
print(len(keys(m))) // -> 0
|
||||
|
||||
// Nil: always Nil
|
||||
n = cast("x", "Nil")
|
||||
print(typeof(n)) // -> "Nil"
|
||||
|
||||
// Function: functions stay functions; others -> Nil
|
||||
fun foo()
|
||||
return 7
|
||||
|
||||
print(typeof(cast(foo, "Function"))) // -> "Function"
|
||||
print(typeof(cast(42, "Function"))) // -> "Nil"
|
||||
|
||||
// Chaining casts
|
||||
print(cast(cast("42", "Number"), "String")) // -> "42"
|
||||
|
||||
print("=== Done ===")
|
||||
|
|
@ -92,6 +92,8 @@ static const char *opcode_name(OpCode op) {
|
|||
case OP_SLICE: return "SLICE";
|
||||
case OP_TO_NUMBER: return "TO_NUMBER";
|
||||
case OP_TO_STRING: return "TO_STRING";
|
||||
case OP_TYPEOF: return "TYPEOF";
|
||||
case OP_CAST: return "CAST";
|
||||
case OP_SPLIT: return "SPLIT";
|
||||
case OP_JOIN: return "JOIN";
|
||||
case OP_SUBSTR: return "SUBSTR";
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ typedef enum {
|
|||
// conversions
|
||||
OP_TO_NUMBER, // pops any; pushes int (parse strings)
|
||||
OP_TO_STRING, // pops any; pushes string
|
||||
OP_CAST, // pops typeName, value; pushes casted value (see vm/cast.c)
|
||||
OP_TYPEOF, // pops any; pushes string name of type
|
||||
OP_UCLAMP, // pops number; pushes number masked to N bits (operand = bits)
|
||||
OP_SCLAMP, // pops number; pushes number clamped to signed N-bit range (operand = bits)
|
||||
|
|
|
|||
125
src/parser.c
125
src/parser.c
|
|
@ -463,9 +463,18 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "cast") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
/* cast(value, typeName) */
|
||||
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "cast expects (value, typeName)"); free(name); return 0; }
|
||||
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "cast expects (value, typeName)"); free(name); return 0; }
|
||||
bytecode_add_instruction(bc, OP_CAST, 0);
|
||||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "typeof") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
/* Special-case: typeof(<identifier>) -> use declared integer subtype if available */
|
||||
/* Disable compile-time shortcut for typeof(<identifier>); always evaluate at runtime */
|
||||
size_t peek = *pos;
|
||||
char *vname = NULL;
|
||||
int handled = 0;
|
||||
|
|
@ -473,109 +482,9 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
/* allow spaces before ')' */
|
||||
skip_spaces(src, len, &peek);
|
||||
if (peek < len && src[peek] == ')') {
|
||||
/* Find declared type metadata for the identifier */
|
||||
int decl_bits = 0; /* >0 unsigned width, <0 signed width, 0 unknown/non-integer */
|
||||
int lidx2 = local_find(vname);
|
||||
int is_class_name = 0;
|
||||
int gidx2 = -1;
|
||||
if (lidx2 >= 0 && g_locals) {
|
||||
decl_bits = g_locals->types[lidx2];
|
||||
} else {
|
||||
/* lookup existing global without creating a new symbol */
|
||||
for (int gi_ = 0; gi_ < G.count; ++gi_) {
|
||||
if (strcmp(G.names[gi_], vname) == 0) { gidx2 = gi_; break; }
|
||||
}
|
||||
if (gidx2 >= 0) {
|
||||
decl_bits = G.types[gidx2];
|
||||
is_class_name = G.is_class[gidx2];
|
||||
}
|
||||
}
|
||||
|
||||
/* Map declared metadata to human-readable typeof */
|
||||
if (decl_bits == TYPE_META_STRING) {
|
||||
int ci = bytecode_add_constant(bc, make_string("String"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
||||
} else if (decl_bits == TYPE_META_NIL) {
|
||||
int ci = bytecode_add_constant(bc, make_string("Nil"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
||||
} else if (decl_bits == TYPE_META_CLASS || is_class_name) {
|
||||
int ci = bytecode_add_constant(bc, make_string("Class"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
||||
} else if (decl_bits == TYPE_META_BOOLEAN) {
|
||||
/* Booleans are numeric 0/1 in Fun */
|
||||
int ci = bytecode_add_constant(bc, make_string("Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
||||
} else if (decl_bits == 8 || decl_bits == 16 || decl_bits == 32 || decl_bits == 64
|
||||
|| decl_bits == -8 || decl_bits == -16 || decl_bits == -32 || decl_bits == -64) {
|
||||
int bits = decl_bits < 0 ? -decl_bits : decl_bits;
|
||||
int is_signed = (decl_bits < 0);
|
||||
char tbuf[16];
|
||||
snprintf(tbuf, sizeof(tbuf), "%s%d", is_signed ? "Sint" : "Uint", bits);
|
||||
int ci = bytecode_add_constant(bc, make_string(tbuf));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
||||
} else {
|
||||
/* Fallback: load the variable, but if it's a class instance (Map with "__class"), return "Class" */
|
||||
if (lidx2 >= 0) {
|
||||
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx2);
|
||||
} else {
|
||||
/* If global not yet present, create symbol to load its value (likely nil) */
|
||||
int gi2 = -1;
|
||||
/* try to reuse existing id if found earlier */
|
||||
for (int gi_ = 0; gi_ < G.count; ++gi_) {
|
||||
if (strcmp(G.names[gi_], vname) == 0) { gi2 = gi_; break; }
|
||||
}
|
||||
if (gi2 < 0) gi2 = sym_index(vname);
|
||||
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi2);
|
||||
}
|
||||
/* Stack: [v] */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [v, tname] */
|
||||
{
|
||||
int ciMap = bytecode_add_constant(bc, make_string("Map"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); /* [v, tname, "Map"] */
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_EQ, 0); /* [v, isMap] */
|
||||
int j_if_not_map2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
|
||||
/* Map branch: check for __class tag */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
|
||||
{
|
||||
int kci = bytecode_add_constant(bc, make_string("__class"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, kci); /* [v, v, "__class"] */
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_HAS_KEY, 0); /* [v, has] */
|
||||
int j_no_meta2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
/* has __class -> call toString() and return */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
|
||||
{
|
||||
int kcits = bytecode_add_constant(bc, make_string("toString"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, kcits); /* [v, v, "toString"] */
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* [v, func] */
|
||||
bytecode_add_instruction(bc, OP_SWAP, 0); /* [func, v] */
|
||||
bytecode_add_instruction(bc, OP_CALL, 1); /* [string] */
|
||||
int j_end2 = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
|
||||
/* no meta: drop v and return "Map" */
|
||||
bytecode_set_operand(bc, j_no_meta2, bc->instr_count);
|
||||
bytecode_add_instruction(bc, OP_POP, 0); /* [] */
|
||||
{
|
||||
int ciMap2 = bytecode_add_constant(bc, make_string("Map"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap2);
|
||||
}
|
||||
int after_map2 = bc->instr_count;
|
||||
|
||||
/* not map: typeof(v) */
|
||||
bytecode_set_operand(bc, j_if_not_map2, after_map2);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
|
||||
/* end */
|
||||
bytecode_set_operand(bc, j_end2, bc->instr_count);
|
||||
}
|
||||
/* Fall back to runtime evaluation path */
|
||||
free(vname);
|
||||
/* consume the ')' */
|
||||
*pos = peek + 1;
|
||||
handled = 1;
|
||||
/* handled remains 0 so we go to general-case below */
|
||||
} else {
|
||||
/* not a simple identifier-only typeof */
|
||||
free(vname);
|
||||
|
|
@ -585,14 +494,6 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
if (!handled) {
|
||||
/* General case: typeof(expression)
|
||||
If the value is a Map with "__class" key, return that string; else return base typeof.
|
||||
Stack plan:
|
||||
- eval expr -> [v]
|
||||
- DUP, TYPEOF, "Map" EQ -> [v, isMap]
|
||||
- if not map: drop condition path and return TYPEOF(v)
|
||||
- if map:
|
||||
DUP, "__class", HAS_KEY -> [v, has]
|
||||
if has: "__class", INDEX_GET -> [className]; return
|
||||
else: POP v; return "Map"
|
||||
*/
|
||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "typeof expects 1 argument"); free(name); return 0; }
|
||||
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after typeof arg"); free(name); return 0; }
|
||||
|
|
@ -637,8 +538,6 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
|
||||
/* not map: compute typeof(v) */
|
||||
bytecode_set_operand(bc, j_if_not_map, after_map);
|
||||
/* Stack currently holds what after we patched? For not-map path, we still have [v] retained because JUMP_IF_FALSE popped cond.
|
||||
Now produce typeof(v). */
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [tname] */
|
||||
|
||||
/* end */
|
||||
|
|
|
|||
1
src/vm.c
1
src/vm.c
|
|
@ -321,6 +321,7 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
#include "vm/print.c"
|
||||
#include "vm/to_number.c"
|
||||
#include "vm/to_string.c"
|
||||
#include "vm/cast.c"
|
||||
#include "vm/typeof.c"
|
||||
#include "vm/uclamp.c"
|
||||
#include "vm/sclamp.c"
|
||||
|
|
|
|||
2
src/vm.h
2
src/vm.h
|
|
@ -27,7 +27,7 @@ static const char *opcode_names[] = {
|
|||
"MOD","AND","OR","NOT","DUP","SWAP",
|
||||
"MAKE_ARRAY","INDEX_GET","INDEX_SET",
|
||||
"LEN","PUSH","APOP","SET","INSERT","REMOVE","SLICE",
|
||||
"TO_NUMBER","TO_STRING","TYPEOF",
|
||||
"TO_NUMBER","TO_STRING","CAST","TYPEOF",
|
||||
"SPLIT","JOIN","SUBSTR","FIND",
|
||||
"CONTAINS","INDEX_OF","CLEAR",
|
||||
"ENUMERATE","ZIP",
|
||||
|
|
|
|||
89
src/vm/cast.c
Normal file
89
src/vm/cast.c
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file cast.c
|
||||
* @brief Implements the OP_CAST opcode for converting a value to a target type.
|
||||
*
|
||||
* Stack contract:
|
||||
* - Pops: typeName (String), value (any)
|
||||
* - Pushes: converted value (or Nil on unsupported target)
|
||||
*/
|
||||
|
||||
case OP_CAST: {
|
||||
/* pop type then value (args pushed in this order: value, typeName) */
|
||||
Value t = pop_value(vm);
|
||||
Value v = pop_value(vm);
|
||||
|
||||
const char *tn = (t.type == VAL_STRING && t.s) ? t.s : NULL;
|
||||
Value out = make_nil();
|
||||
|
||||
/* Normalize target name to lowercase into a small buffer */
|
||||
char target[32];
|
||||
int k = 0;
|
||||
if (tn) {
|
||||
const char *p = tn;
|
||||
while (*p && k < (int)sizeof(target) - 1) {
|
||||
char c = *p++;
|
||||
if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
|
||||
target[k++] = c;
|
||||
}
|
||||
}
|
||||
target[k] = '\0';
|
||||
|
||||
if (!tn) {
|
||||
out = make_nil();
|
||||
} else if (strcmp(target, "number") == 0) {
|
||||
if (v.type == VAL_INT) {
|
||||
out = make_int(v.i);
|
||||
} else if (v.type == VAL_STRING) {
|
||||
const char *s = v.s ? v.s : "";
|
||||
const char *p = s;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
|
||||
char *endp = NULL;
|
||||
long long parsed = strtoll(p, &endp, 10);
|
||||
while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++;
|
||||
if (endp && *endp != '\0') out = make_int(0);
|
||||
else out = make_int((int64_t)parsed);
|
||||
} else {
|
||||
out = make_int(0);
|
||||
}
|
||||
} else if (strcmp(target, "string") == 0) {
|
||||
char *s = value_to_string_alloc(&v);
|
||||
out = make_string(s ? s : "");
|
||||
if (s) free(s);
|
||||
} else if (strcmp(target, "array") == 0) {
|
||||
if (v.type == VAL_ARRAY) {
|
||||
out = copy_value(&v);
|
||||
} else {
|
||||
Value tmp = deep_copy_value(&v);
|
||||
out = make_array_from_values(&tmp, 1);
|
||||
free_value(tmp);
|
||||
}
|
||||
} else if (strcmp(target, "map") == 0) {
|
||||
if (v.type == VAL_MAP) {
|
||||
out = copy_value(&v);
|
||||
} else {
|
||||
out = make_map_empty();
|
||||
}
|
||||
} else if (strcmp(target, "nil") == 0) {
|
||||
out = make_nil();
|
||||
} else if (strcmp(target, "function") == 0) {
|
||||
out = (v.type == VAL_FUNCTION) ? copy_value(&v) : make_nil();
|
||||
} else if (strcmp(target, "boolean") == 0) {
|
||||
out = make_int(value_is_truthy(&v) ? 1 : 0);
|
||||
} else {
|
||||
out = make_nil();
|
||||
}
|
||||
|
||||
free_value(t);
|
||||
free_value(v);
|
||||
push_value(vm, out);
|
||||
break;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue