Skip to content

Commit 8b848ab

Browse files
gh-33: Fully enforce static typing.
1 parent 50860ec commit 8b848ab

File tree

7 files changed

+115
-35
lines changed

7 files changed

+115
-35
lines changed

docs/SPECIFICATION.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@
211211
212212
MAP and `TNS` values are reference (aliasing) types: assigning a `MAP` or `TNS` to another identifier copies a reference to the same underlying container rather than performing an implicit deep copy. Mutating a map (via `map<...> = ...`, `DEL`, or other mutating operators) or mutating a tensor element through indexed assignment will be observed through any other identifier that references the same container. Use the built-in `COPY` (shallow copy) or `DEEPCOPY` (recursive deep copy) operators when a non-aliased duplicate is required.
213213
214-
Every runtime value has a static type: `INT`, `FLT`, `STR`, `TNS`, or `FUNC`. Integers are conceptually unbounded mathematical integers. Floats are IEEE754 binary floating-point numbers. Strings are sequences of characters (source text is ASCII, but escape codes may denote non-ASCII code points). Tensors are non-scalar aggregates whose elements may be `INT`, `FLT`, `STR`, `FUNC`, or `TNS`.
214+
Every runtime value has a static type: `INT`, `FLT`, `STR`, `TNS`, `MAP`, `THR`, or `FUNC`. Integers are conceptually unbounded mathematical integers. Floats are IEEE754 binary floating-point numbers. Strings are sequences of characters (source text is ASCII, but escape codes may denote non-ASCII code points). Tensors are non-scalar aggregates whose elements may be `INT`, `FLT`, `STR`, `MAP`, `THR`, `FUNC`, or `TNS`. Maps are associative containers mapping scalar keys (`INT`, `FLT`, or `STR`) to values of a single static type. Threads are handles to parallel code blocks. Functions are user-defined code blocks with lexical closures.
215215
216216
Function value (`FUNC`): a reference to a user-defined function body (including its lexical closure). A `FUNC` value can be stored in variables or tensors, passed as an argument, or returned from a function. The call syntax applies to any expression that evaluates to `FUNC`; for example, `alias()` calls the function bound to `alias`, and `tns[1]()` calls the function stored in that tensor element. `FUNC` values are always truthy; equality compares object identity (two references are equal only if they refer to the same function definition). String rendering produces an implementation-defined placeholder such as `<func name>`.
217217

src/ast.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ typedef enum {
88
TYPE_FLT,
99
TYPE_STR,
1010
TYPE_TNS,
11+
TYPE_MAP,
1112
TYPE_FUNC,
1213
TYPE_THR,
1314
TYPE_UNKNOWN

src/builtins.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ static const char* decl_type_name(DeclType dt) {
276276
case TYPE_FLT: return "FLT";
277277
case TYPE_STR: return "STR";
278278
case TYPE_TNS: return "TNS";
279+
case TYPE_MAP: return "MAP";
279280
case TYPE_FUNC: return "FUNC";
280281
case TYPE_THR: return "THR";
281282
default: return "UNKNOWN";
@@ -288,6 +289,7 @@ static DeclType decl_type_from_name(const char* name) {
288289
if (strcmp(name, "FLT") == 0) return TYPE_FLT;
289290
if (strcmp(name, "STR") == 0) return TYPE_STR;
290291
if (strcmp(name, "TNS") == 0) return TYPE_TNS;
292+
if (strcmp(name, "MAP") == 0) return TYPE_MAP;
291293
if (strcmp(name, "FUNC") == 0) return TYPE_FUNC;
292294
if (strcmp(name, "THR") == 0) return TYPE_THR;
293295
return TYPE_UNKNOWN;
@@ -4932,6 +4934,7 @@ static Value builtin_signature(Interpreter* interp, Value* args, int argc, Expr*
49324934
case TYPE_FLT: tname = "FLT"; break;
49334935
case TYPE_STR: tname = "STR"; break;
49344936
case TYPE_TNS: tname = "TNS"; break;
4937+
case TYPE_MAP: tname = "MAP"; break;
49354938
case TYPE_FUNC: tname = "FUNC"; break;
49364939
case TYPE_THR: tname = "THR"; break;
49374940
default: tname = "ANY"; break;
@@ -4977,6 +4980,7 @@ static Value builtin_signature(Interpreter* interp, Value* args, int argc, Expr*
49774980
case TYPE_FLT: rname = "FLT"; break;
49784981
case TYPE_STR: rname = "STR"; break;
49794982
case TYPE_TNS: rname = "TNS"; break;
4983+
case TYPE_MAP: rname = "MAP"; break;
49804984
case TYPE_FUNC: rname = "FUNC"; break;
49814985
case TYPE_THR: rname = "THR"; break;
49824986
default: rname = "ANY"; break;
@@ -4998,6 +5002,7 @@ static Value builtin_signature(Interpreter* interp, Value* args, int argc, Expr*
49985002
case TYPE_FLT: tname = "FLT"; break;
49995003
case TYPE_STR: tname = "STR"; break;
50005004
case TYPE_TNS: tname = "TNS"; break;
5005+
case TYPE_MAP: tname = "MAP"; break;
50015006
case TYPE_FUNC: tname = "FUNC"; break;
50025007
case TYPE_THR: tname = "THR"; break;
50035008
default: tname = value_type_name(entry->value); break;
@@ -5781,6 +5786,7 @@ static Value builtin_assign(Interpreter* interp, Value* args, int argc, Expr** a
57815786
case VAL_FLT: actual = TYPE_FLT; break;
57825787
case VAL_STR: actual = TYPE_STR; break;
57835788
case VAL_TNS: actual = TYPE_TNS; break;
5789+
case VAL_MAP: actual = TYPE_MAP; break;
57845790
case VAL_FUNC: actual = TYPE_FUNC; break;
57855791
case VAL_THR: actual = TYPE_THR; break;
57865792
default: actual = TYPE_UNKNOWN; break;

src/env.c

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,19 @@ static int env_permafrozen_raw(Env* env, const char* name) {
188188
return entry->permafrozen ? 1 : 0;
189189
}
190190

191+
static DeclType env_decl_type_from_value(Value value) {
192+
switch (value.type) {
193+
case VAL_INT: return TYPE_INT;
194+
case VAL_FLT: return TYPE_FLT;
195+
case VAL_STR: return TYPE_STR;
196+
case VAL_TNS: return TYPE_TNS;
197+
case VAL_MAP: return TYPE_MAP;
198+
case VAL_FUNC: return TYPE_FUNC;
199+
case VAL_THR: return TYPE_THR;
200+
default: return TYPE_UNKNOWN;
201+
}
202+
}
203+
191204
/* ================================================================== */
192205
/* Direct (unbuffered) write implementations */
193206
/* Called by the prepare thread or when the buffer is inactive. */
@@ -225,6 +238,12 @@ bool env_assign_direct(Env* env, const char* name, Value value,
225238
const char* target_name = entry->alias_target;
226239
EnvEntry* target = env_get_entry_raw(env, target_name);
227240
if (!target) return false;
241+
if (type != TYPE_UNKNOWN && target->decl_type != type) return false;
242+
DeclType actual_type = env_decl_type_from_value(value);
243+
if (target->decl_type != TYPE_UNKNOWN && actual_type != TYPE_UNKNOWN &&
244+
target->decl_type != actual_type) {
245+
return false;
246+
}
228247
if (target->frozen || target->permafrozen) return false;
229248
if (target->initialized) value_free(target->value);
230249
target->value = value_copy(value);
@@ -235,15 +254,29 @@ bool env_assign_direct(Env* env, const char* name, Value value,
235254
/* Respect frozen / permafrozen bindings */
236255
if (entry->frozen || entry->permafrozen) return false;
237256

257+
if (type != TYPE_UNKNOWN && entry->decl_type != type) return false;
258+
259+
DeclType actual_type = env_decl_type_from_value(value);
260+
if (entry->decl_type != TYPE_UNKNOWN && actual_type != TYPE_UNKNOWN &&
261+
entry->decl_type != actual_type) {
262+
return false;
263+
}
264+
238265
if (entry->initialized) value_free(entry->value);
239266
entry->value = value_copy(value);
240267
entry->initialized = true;
241268
return true;
242269
}
243270
}
244271
if (!declare_if_missing) return false;
245-
env_define_direct(env, name, type);
272+
if (type == TYPE_UNKNOWN) return false;
273+
DeclType actual_type = env_decl_type_from_value(value);
274+
if (actual_type != TYPE_UNKNOWN && actual_type != type) {
275+
return false;
276+
}
277+
if (!env_define_direct(env, name, type)) return false;
246278
EnvEntry* entry = env_find_local(env, name);
279+
if (!entry) return false;
247280
entry->value = value_copy(value);
248281
entry->initialized = true;
249282
return true;
@@ -489,4 +522,4 @@ int env_entry_frozen_state_local(EnvEntry* entry) {
489522
if (entry->permafrozen) return -1;
490523
if (entry->frozen) return 1;
491524
return 0;
492-
}
525+
}

src/interpreter.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,18 +472,33 @@ static DeclType value_type_to_decl(ValueType vt) {
472472
case VAL_FLT: return TYPE_FLT;
473473
case VAL_STR: return TYPE_STR;
474474
case VAL_TNS: return TYPE_TNS;
475+
case VAL_MAP: return TYPE_MAP;
475476
case VAL_FUNC: return TYPE_FUNC;
476477
case VAL_THR: return TYPE_THR;
477478
default: return TYPE_UNKNOWN;
478479
}
479480
}
480481

482+
static const char* decl_type_name(DeclType dt) {
483+
switch (dt) {
484+
case TYPE_INT: return "INT";
485+
case TYPE_FLT: return "FLT";
486+
case TYPE_STR: return "STR";
487+
case TYPE_TNS: return "TNS";
488+
case TYPE_MAP: return "MAP";
489+
case TYPE_FUNC: return "FUNC";
490+
case TYPE_THR: return "THR";
491+
default: return "UNKNOWN";
492+
}
493+
}
494+
481495
static ValueType decl_type_to_value(DeclType dt) {
482496
switch (dt) {
483497
case TYPE_INT: return VAL_INT;
484498
case TYPE_FLT: return VAL_FLT;
485499
case TYPE_STR: return VAL_STR;
486500
case TYPE_TNS: return VAL_TNS;
501+
case TYPE_MAP: return VAL_MAP;
487502
case TYPE_FUNC: return VAL_FUNC;
488503
case TYPE_THR: return VAL_THR;
489504
default: return VAL_NULL;
@@ -1846,6 +1861,7 @@ static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap*
18461861
expected == TYPE_FLT ? "FLT" :
18471862
expected == TYPE_STR ? "STR" :
18481863
expected == TYPE_TNS ? "TNS" :
1864+
expected == TYPE_MAP ? "MAP" :
18491865
expected == TYPE_FUNC ? "FUNC" :
18501866
expected == TYPE_THR ? "THR" : "UNKNOWN",
18511867
value_type_name(v));
@@ -1854,6 +1870,13 @@ static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap*
18541870
}
18551871

18561872
EnvEntry* existing = env_get_entry(env, stmt->as.assign.name);
1873+
if (existing && existing->decl_type != expected) {
1874+
char buf[128];
1875+
snprintf(buf, sizeof(buf), "Type mismatch: expected %s but got %s",
1876+
decl_type_name(existing->decl_type), decl_type_name(expected));
1877+
value_free(v);
1878+
return make_error(buf, stmt->line, stmt->column);
1879+
}
18571880
Env* assign_env = env;
18581881
if (!interp->isolate_env_writes && !existing && env->parent) {
18591882
assign_env = env->parent;
@@ -1862,6 +1885,14 @@ static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap*
18621885
env_define(assign_env, stmt->as.assign.name, expected);
18631886
}
18641887
if (!env_assign(assign_env, stmt->as.assign.name, v, expected, true)) {
1888+
EnvEntry* echeck = env_get_entry(assign_env, stmt->as.assign.name);
1889+
if (echeck && echeck->decl_type != actual) {
1890+
char buf[128];
1891+
snprintf(buf, sizeof(buf), "Type mismatch: expected %s but got %s",
1892+
decl_type_name(echeck->decl_type), value_type_name(v));
1893+
value_free(v);
1894+
return make_error(buf, stmt->line, stmt->column);
1895+
}
18651896
char buf[256];
18661897
snprintf(buf, sizeof(buf), "Cannot assign to frozen identifier '%s'", stmt->as.assign.name);
18671898
value_free(v);
@@ -1871,6 +1902,14 @@ static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap*
18711902
if (!env_assign(env, stmt->as.assign.name, v, TYPE_UNKNOWN, false)) {
18721903
EnvEntry* echeck = env_get_entry(env, stmt->as.assign.name);
18731904
if (echeck) {
1905+
DeclType actual = value_type_to_decl(v.type);
1906+
if (echeck->decl_type != TYPE_UNKNOWN && echeck->decl_type != actual) {
1907+
char buf[128];
1908+
snprintf(buf, sizeof(buf), "Type mismatch: expected %s but got %s",
1909+
decl_type_name(echeck->decl_type), value_type_name(v));
1910+
value_free(v);
1911+
return make_error(buf, stmt->line, stmt->column);
1912+
}
18741913
char buf[256];
18751914
snprintf(buf, sizeof(buf), "Cannot assign to frozen identifier '%s'", stmt->as.assign.name);
18761915
value_free(v);

src/parser.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ static DeclType parse_type_name(const char* name) {
6464
if (strcmp(name, "INT") == 0) return TYPE_INT;
6565
if (strcmp(name, "FLT") == 0) return TYPE_FLT;
6666
if (strcmp(name, "STR") == 0) return TYPE_STR;
67+
if (strcmp(name, "MAP") == 0) return TYPE_MAP;
6768
if (strcmp(name, "FUNC") == 0) return TYPE_FUNC;
6869
if (strcmp(name, "THR") == 0) return TYPE_THR;
6970
if (strcmp(name, "TNS") == 0) return TYPE_TNS;

tests/test2.pre

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -301,27 +301,27 @@ PRINT("Tensor-scalar arithmetic: PASS\n")
301301

302302
! --- Matrix (elementwise) arithmetic operators ---
303303
PRINT("Testing matrix (elementwise) arithmetic...")
304-
TNS: m1 = [[1, 10], [11, 100]]
305-
TNS: m2 = [[10, 1], [100, 11]]
306-
ASSERT(EQ(MADD(m1, m2), [[11, 11], [111, 111]]))
307-
ASSERT(EQ(MSUB(m1, m2), [[-1, 1], [-1, 1]]))
308-
ASSERT(EQ(MMUL(m1, m2), [[10, 10], [1100, 1100]]))
309-
ASSERT(EQ(MDIV(m1, m2), [[0, 10], [0, 1]]))
310-
DEL(m1)
311-
DEL(m2)
304+
TNS: t1 = [[1, 10], [11, 100]]
305+
TNS: t2 = [[10, 1], [100, 11]]
306+
ASSERT(EQ(MADD(t1, t2), [[11, 11], [111, 111]]))
307+
ASSERT(EQ(MSUB(t1, t2), [[-1, 1], [-1, 1]]))
308+
ASSERT(EQ(MMUL(t1, t2), [[10, 10], [1100, 1100]]))
309+
ASSERT(EQ(MDIV(t1, t2), [[0, 10], [0, 1]]))
310+
DEL(t1)
311+
DEL(t2)
312312
PRINT("Matrix arithmetic: PASS\n")
313313

314314
! --- MSUM / MPROD ---
315315
PRINT("Testing MSUM/MPROD...")
316-
TNS: ma1 = [[1, 10], [11, 100]]
317-
TNS: ma2 = [[10, 1], [100, 11]]
318-
TNS: ma3 = [[1, 1], [1, 1]]
319-
ASSERT(EQ(MSUM(ma1, ma2, ma3), [[100, 100], [1000, 1000]]))
320-
ASSERT(EQ(MSUM(ma1, ma2), [[11, 11], [111, 111]]))
321-
ASSERT(EQ(MPROD(ma1, ma2, ma3), [[10, 10], [1100, 1100]]))
322-
DEL(ma1)
323-
DEL(ma2)
324-
DEL(ma3)
316+
TNS: t1 = [[1, 10], [11, 100]]
317+
TNS: t2 = [[10, 1], [100, 11]]
318+
TNS: t3 = [[1, 1], [1, 1]]
319+
ASSERT(EQ(MSUM(t1, t2, t3), [[100, 100], [1000, 1000]]))
320+
ASSERT(EQ(MSUM(t1, t2), [[11, 11], [111, 111]]))
321+
ASSERT(EQ(MPROD(t1, t2, t3), [[10, 10], [1100, 1100]]))
322+
DEL(t1)
323+
DEL(t2)
324+
DEL(t3)
325325
PRINT("MSUM/MPROD: PASS\n")
326326

327327
! --- Arithmetic operators ---
@@ -390,21 +390,21 @@ PRINT("BYTES: PASS\n")
390390

391391
! --- Type checking ---
392392
PRINT("Testing type checks...")
393-
INT: x = 101
394-
FLT: y = 1.1
395-
STR: z = "test"
396-
ASSERT(ISINT(x))
397-
ASSERT(NOT(ISINT(y)))
398-
ASSERT(ISFLT(y))
399-
ASSERT(NOT(ISFLT(x)))
400-
ASSERT(ISSTR(z))
401-
ASSERT(NOT(ISSTR(x)))
402-
ASSERT(EQ(TYPE(x), "INT"))
403-
ASSERT(EQ(TYPE(y), "FLT"))
404-
ASSERT(EQ(TYPE(z), "STR"))
405-
DEL(x)
406-
DEL(y)
407-
DEL(z)
393+
INT: i1 = 101
394+
FLT: f1 = 1.1
395+
STR: s1 = "test"
396+
ASSERT(ISINT(i1))
397+
ASSERT(NOT(ISINT(f1)))
398+
ASSERT(ISFLT(f1))
399+
ASSERT(NOT(ISFLT(i1)))
400+
ASSERT(ISSTR(s1))
401+
ASSERT(NOT(ISSTR(i1)))
402+
ASSERT(EQ(TYPE(i1), "INT"))
403+
ASSERT(EQ(TYPE(f1), "FLT"))
404+
ASSERT(EQ(TYPE(s1), "STR"))
405+
DEL(i1)
406+
DEL(f1)
407+
DEL(s1)
408408
PRINT("Type checks: PASS\n")
409409

410410
! --- String operations ---

0 commit comments

Comments
 (0)