Classes are a real type class now and some fixes. (0.10.0)
This commit is contained in:
parent
2a1b74f13f
commit
5d3cfd9f65
12 changed files with 203 additions and 41 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fun VERSION 0.9.3 LANGUAGES C)
|
||||
project(fun VERSION 0.10.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
0
examples/byte_for_demo.fun
Normal file → Executable file
0
examples/byte_for_demo.fun
Normal file → Executable file
0
examples/classes_demo.fun
Normal file → Executable file
0
examples/classes_demo.fun
Normal file → Executable file
0
examples/include_lib.fun
Normal file → Executable file
0
examples/include_lib.fun
Normal file → Executable file
0
examples/include_local.fun
Normal file → Executable file
0
examples/include_local.fun
Normal file → Executable file
0
examples/os_env.fun
Normal file → Executable file
0
examples/os_env.fun
Normal file → Executable file
0
examples/type_safety.fun
Normal file → Executable file
0
examples/type_safety.fun
Normal file → Executable file
0
examples/type_safety_fails.fun
Normal file → Executable file
0
examples/type_safety_fails.fun
Normal file → Executable file
79
examples/types_overview.fun
Executable file
79
examples/types_overview.fun
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
print("=== Dynamic (untyped) ===")
|
||||
x = 123
|
||||
print(typeof(x)) // -> "Number"
|
||||
x = "ok"
|
||||
print(typeof(x)) // -> "String"
|
||||
|
||||
print("")
|
||||
print("=== Numbers and integer subtypes ===")
|
||||
number n = 42
|
||||
print(typeof(n)) // -> "Number"
|
||||
print(n) // -> 42
|
||||
|
||||
int8 s8 = 130 // clamps to 127
|
||||
print(s8) // -> 127
|
||||
uint16 u16 = 70000 // clamps to 65535
|
||||
print(u16) // -> 65535
|
||||
byte b = 0xFF // 255
|
||||
print(b) // -> 255
|
||||
boolean flag = 2 // clamps to 1
|
||||
print(flag) // -> 1
|
||||
|
||||
print("")
|
||||
print("=== Strings ===")
|
||||
string s = "hello"
|
||||
print(typeof(s)) // -> "String"
|
||||
print(s) // -> "hello"
|
||||
|
||||
print("")
|
||||
print("=== Nil ===")
|
||||
nil nn = nil
|
||||
print(typeof(nn)) // -> "Nil"
|
||||
print(nn) // -> nil
|
||||
|
||||
print("")
|
||||
print("=== Arrays ===")
|
||||
arr = [1, 2, 3]
|
||||
print(typeof(arr)) // -> "Array"
|
||||
print(len(arr)) // -> 3
|
||||
print(arr[1]) // -> 2
|
||||
print(arr[0:2]) // -> [1, 2]
|
||||
|
||||
print("")
|
||||
print("=== Maps ===")
|
||||
m = { "k": "v", "n": 1 }
|
||||
print(typeof(m)) // -> "Map"
|
||||
print(m["k"]) // -> "v"
|
||||
|
||||
print("")
|
||||
print("=== Functions ===")
|
||||
fun add(a, b)
|
||||
return a + b
|
||||
print(typeof(add)) // -> "Function"
|
||||
print(add(5, 7)) // -> 12
|
||||
|
||||
print("")
|
||||
print("=== Classes and Class-typed variables ===")
|
||||
class Person(number age, string name)
|
||||
// methods must declare 'this' as first parameter
|
||||
fun toString(this)
|
||||
// typeof(instance) will call this and return it
|
||||
return "Class"
|
||||
|
||||
Class p = Person(33, "Jo")
|
||||
print(typeof(p)) // -> "Class"
|
||||
// p = "nope" // Uncomment to see TypeError: expected Class
|
||||
|
||||
print("")
|
||||
print("=== Done ===")
|
||||
105
src/parser.c
105
src/parser.c
|
|
@ -65,10 +65,12 @@ static int g_temp_counter = 0;
|
|||
/* Declared type metadata encoding in types[]:
|
||||
0 = dynamic/untyped;
|
||||
positive/negative 8/16/32/64 = integers (negative means signed);
|
||||
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types. */
|
||||
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types;
|
||||
TYPE_META_CLASS marks class instances (Map with "__class"). */
|
||||
#define TYPE_META_STRING 10001
|
||||
#define TYPE_META_BOOLEAN 10002
|
||||
#define TYPE_META_NIL 10003
|
||||
#define TYPE_META_CLASS 10004
|
||||
|
||||
static void parser_fail(size_t pos, const char *fmt, ...) {
|
||||
g_has_error = 1;
|
||||
|
|
@ -489,16 +491,28 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
}
|
||||
}
|
||||
|
||||
if (decl_bits != 0) {
|
||||
/* 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 if (is_class_name) {
|
||||
int ci = bytecode_add_constant(bc, make_string("Class"));
|
||||
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) {
|
||||
|
|
@ -1752,10 +1766,11 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
}
|
||||
|
||||
/* typed declarations:
|
||||
number|string|boolean|nil|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 <ident> (= expr)?
|
||||
Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit.
|
||||
number|string|boolean|nil|Class|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 <ident> (= expr)?
|
||||
Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. 'Class' restricts to class instances.
|
||||
*/
|
||||
if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0
|
||||
|| strcmp(name, "Class") == 0
|
||||
|| strcmp(name, "byte") == 0
|
||||
|| strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0
|
||||
|| strcmp(name, "int8") == 0 || strcmp(name, "int16") == 0 || strcmp(name, "int32") == 0 || strcmp(name, "int64") == 0) {
|
||||
|
|
@ -1763,6 +1778,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
int is_string = (strcmp(name, "string") == 0);
|
||||
int is_boolean = (strcmp(name, "boolean") == 0);
|
||||
int is_nil = (strcmp(name, "nil") == 0);
|
||||
int is_class_tkn = (strcmp(name, "Class") == 0);
|
||||
int is_byte = (strcmp(name, "byte") == 0);
|
||||
int is_u8 = (strcmp(name, "uint8") == 0) || is_byte;
|
||||
int is_u16 = (strcmp(name, "uint16") == 0);
|
||||
|
|
@ -1778,7 +1794,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
|
||||
if (decl_signed) decl_bits = -decl_bits;
|
||||
|
||||
/* declared type metadata: integers use decl_bits; string/boolean/nil use special markers */
|
||||
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class use special markers */
|
||||
int decl_meta = decl_bits;
|
||||
if (is_string) {
|
||||
decl_meta = TYPE_META_STRING;
|
||||
|
|
@ -1786,6 +1802,8 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
decl_meta = TYPE_META_BOOLEAN;
|
||||
} else if (is_nil) {
|
||||
decl_meta = TYPE_META_NIL;
|
||||
} else if (is_class_tkn) {
|
||||
decl_meta = TYPE_META_CLASS;
|
||||
}
|
||||
|
||||
free(name);
|
||||
|
|
@ -1843,6 +1861,39 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (decl_meta == TYPE_META_CLASS) {
|
||||
/* expect Class instance: Map with "__class" key */
|
||||
/* Check typeof(v) == "Map" */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
{
|
||||
int ciMap = bytecode_add_constant(bc, make_string("Map"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap);
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
/* Check v has "__class" */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
{
|
||||
int kci = bytecode_add_constant(bc, make_string("__class"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_HAS_KEY, 0);
|
||||
int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
/* Success path jumps over error block */
|
||||
int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
/* Error block */
|
||||
int err_lbl = bc->instr_count;
|
||||
bytecode_set_operand(bc, j_err1, err_lbl);
|
||||
bytecode_set_operand(bc, j_err2, err_lbl);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
/* Continue after checks */
|
||||
bytecode_set_operand(bc, j_ok, bc->instr_count);
|
||||
} else if (decl_meta == TYPE_META_BOOLEAN) {
|
||||
/* expect Number then clamp to 1 bit (unsigned) */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
|
|
@ -1916,6 +1967,9 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
ci = bytecode_add_constant(bc, make_string(""));
|
||||
} else if (is_nil) {
|
||||
ci = bytecode_add_constant(bc, make_nil());
|
||||
} else if (is_class_tkn) {
|
||||
/* Class-typed variable defaults to Nil until assigned an instance */
|
||||
ci = bytecode_add_constant(bc, make_nil());
|
||||
} else if (is_number || is_boolean || (decl_bits != 0)) {
|
||||
/* integers/booleans default to 0 */
|
||||
ci = bytecode_add_constant(bc, make_int(0));
|
||||
|
|
@ -2117,6 +2171,39 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (meta == TYPE_META_CLASS) {
|
||||
/* expect Class instance: Map with "__class" key */
|
||||
/* Check typeof(v) == "Map" */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
{
|
||||
int ciMap = bytecode_add_constant(bc, make_string("Map"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap);
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
/* Check v has "__class" */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
{
|
||||
int kci = bytecode_add_constant(bc, make_string("__class"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_HAS_KEY, 0);
|
||||
int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
/* Success path jumps over error block */
|
||||
int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
/* Error block */
|
||||
int err_lbl = bc->instr_count;
|
||||
bytecode_set_operand(bc, j_err1, err_lbl);
|
||||
bytecode_set_operand(bc, j_err2, err_lbl);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
/* Continue after checks */
|
||||
bytecode_set_operand(bc, j_ok, bc->instr_count);
|
||||
} else if (meta == TYPE_META_BOOLEAN) {
|
||||
/* expect Number then clamp to 1 bit (unsigned) */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
|
|
@ -3274,4 +3361,4 @@ int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *out
|
|||
if (outLine) *outLine = g_err_line;
|
||||
if (outCol) *outCol = g_err_col;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,32 +8,27 @@
|
|||
*/
|
||||
|
||||
case OP_SCLAMP: {
|
||||
/* Clamp/wrap to signed N-bit range: [-2^(N-1), 2^(N-1)-1] */
|
||||
/* Saturating clamp to signed N-bit range: [-2^(N-1) .. 2^(N-1)-1] */
|
||||
Value v = pop_value(vm);
|
||||
int bits = inst.operand;
|
||||
uint64_t u = 0;
|
||||
|
||||
if (v.type == VAL_INT) {
|
||||
u = (uint64_t)v.i;
|
||||
} else {
|
||||
u = 0;
|
||||
}
|
||||
int64_t vi = (v.type == VAL_INT) ? v.i : 0;
|
||||
|
||||
int64_t smin, smax;
|
||||
if (bits <= 0) {
|
||||
/* treat as no-op for invalid widths */
|
||||
} else if (bits >= 64) {
|
||||
/* 64-bit signed: nothing to mask, keep as is */
|
||||
smin = 0; smax = 0;
|
||||
} else if (bits >= 63) {
|
||||
/* cover full int64_t domain for 63+ bits */
|
||||
smin = INT64_MIN;
|
||||
smax = INT64_MAX;
|
||||
} else {
|
||||
uint64_t mask = (1ULL << bits) - 1ULL;
|
||||
u &= mask;
|
||||
uint64_t sign_bit = 1ULL << (bits - 1);
|
||||
if (u & sign_bit) {
|
||||
/* negative value in two's complement */
|
||||
u -= (1ULL << bits);
|
||||
}
|
||||
smin = -(1LL << (bits - 1));
|
||||
smax = (1LL << (bits - 1)) - 1LL;
|
||||
}
|
||||
|
||||
push_value(vm, make_int((int64_t)u));
|
||||
if (vi < smin) vi = smin;
|
||||
else if (vi > smax) vi = smax;
|
||||
|
||||
push_value(vm, make_int(vi));
|
||||
free_value(v);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,24 +8,25 @@
|
|||
*/
|
||||
|
||||
case OP_UCLAMP: {
|
||||
/* Clamp/wrap the integer on top of the stack to 'bits' width (unsigned) */
|
||||
/* Saturating clamp to unsigned N-bit range: [0 .. 2^N - 1] */
|
||||
Value v = pop_value(vm);
|
||||
int bits = inst.operand;
|
||||
uint64_t u = 0;
|
||||
int64_t vi = (v.type == VAL_INT) ? v.i : 0;
|
||||
|
||||
if (v.type == VAL_INT) {
|
||||
u = (uint64_t) v.i;
|
||||
uint64_t umax;
|
||||
if (bits <= 0) {
|
||||
/* treat as clamp to 0..0 */
|
||||
umax = 0;
|
||||
} else if (bits >= 64) {
|
||||
umax = UINT64_MAX;
|
||||
} else {
|
||||
/* Non-integers clamp to 0 */
|
||||
u = 0;
|
||||
umax = (1ULL << bits) - 1ULL;
|
||||
}
|
||||
|
||||
if (bits > 0 && bits < 64) {
|
||||
uint64_t mask = (1ULL << bits) - 1ULL;
|
||||
u &= mask;
|
||||
} else {
|
||||
/* bits >= 64 -> no-op; bits <= 0 -> no-op */
|
||||
}
|
||||
uint64_t u;
|
||||
if (vi < 0) u = 0;
|
||||
else if ((uint64_t)vi > umax) u = umax;
|
||||
else u = (uint64_t)vi;
|
||||
|
||||
push_value(vm, make_int((int64_t)u));
|
||||
free_value(v);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue