changeset 75:a7569b66b12a

built-ins: Implement first built-in function, let.
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 25 Aug 2019 18:46:25 +0200
parents d5330e2b1488
children 1ae2b63e9d65
files src/CMakeLists.txt src/built-ins.c src/built-ins_test.c src/types.h
diffstat 4 files changed, 165 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/src/CMakeLists.txt	Sun Aug 25 18:46:06 2019 +0200
+++ b/src/CMakeLists.txt	Sun Aug 25 18:46:25 2019 +0200
@@ -31,22 +31,28 @@
 TARGET_LINK_LIBRARIES(atom_test core)
 YADD_TEST(atom_test)
 
-# Value test.
-ADD_EXECUTABLE(value_test value_test.c)
-TARGET_LINK_LIBRARIES(value_test core)
-YADD_TEST(value_test)
+# Built-in test.
+ADD_EXECUTABLE(builtin_test built-ins_test.c)
+TARGET_LINK_LIBRARIES(builtin_test core)
+YADD_TEST(builtin_test)
 
 # Expr test.
 ADD_EXECUTABLE(expr_test expr_test.c)
 TARGET_LINK_LIBRARIES(expr_test core)
 YADD_TEST(expr_test)
 
+# Parse test
+ADD_EXECUTABLE(parse_test parse_test.c)
+TARGET_LINK_LIBRARIES(parse_test frontend)
+YADD_TEST(parse_test)
+
 # Print sizes.
 ADD_EXECUTABLE(sizes_test sizes_test.c)
 TARGET_LINK_LIBRARIES(sizes_test core)
 YADD_TEST(sizes_test)
 
-# Parse test
-ADD_EXECUTABLE(parse_test parse_test.c)
-TARGET_LINK_LIBRARIES(parse_test frontend)
-YADD_TEST(parse_test)
+# Value test.
+ADD_EXECUTABLE(value_test value_test.c)
+TARGET_LINK_LIBRARIES(value_test core)
+YADD_TEST(value_test)
+
--- a/src/built-ins.c	Sun Aug 25 18:46:06 2019 +0200
+++ b/src/built-ins.c	Sun Aug 25 18:46:25 2019 +0200
@@ -1,25 +1,101 @@
 #include "built-ins.h"
 
-const char* YBUILTIN_ENUM_STR[] = {
-    "BUILTIN:UNDEF",
-    "BUILTIN:FOR",
-    "BUILTIN:LET",
-    "BUILTIN:DEFN",
-    "BUILTIN:+",
-    "BUILTIN:-",
-    "BUILTIN:*",
-    "BUILTIN:/",
-    "BUILTIN:IF",
-    "BUILTIN:SEQ",
+#include <src/value.h>
+
+#include <src/base/str.h>
+
+// TODO: Write standard library!
+
+struct ybuiltin_id_mapping {
+    const char* id;
+    YBUILTIN_TYPE builtin;
+};
+
+static const struct ybuiltin_id_mapping YBUILTIN_ID_MAPPING[] = {
+    {"__invalid", YBUILTIN_UNDEF}, {"for", YBUILTIN_FOR}, {"let", YBUILTIN_LET},
+    {"defn", YBUILTIN_DEFN},       {"+", YBUILTIN_PLUS},  {"-", YBUILTIN_MINUS},
+    {"*", YBUILTIN_MULT},          {"/", YBUILTIN_DIV},   {"if", YBUILTIN_IF},
+    {"seq", YBUILTIN_SEQ},         {"==", YBUILTIN_EQ},   {"<", YBUILTIN_LT},
+};
+
+static const char* YBUILTIN_ENUM_STR[] = {
+    "BUILTIN:UNDEF", "BUILTIN:FOR", "BUILTIN:LET", "BUILTIN:DEFN",
+
+    "BUILTIN:+",     "BUILTIN:-",   "BUILTIN:*",   "BUILTIN:/",
+
+    "BUILTIN:IF",    "BUILTIN:SEQ",
+
+    "BUILTIN:EQ",    "BUILTIN:LT",
 };
 
+/// Ownership of msg is transferred to this function, ownership of offending is
+/// not.
+static yexpr_t ybuiltin_type_error(YBUILTIN_TYPE self, ystr_t msg,
+                                   yexpr_t* offending) {
+    ystr_t fullmsg = ystr_new(NULL);
+    ystr_t expr = yexpr_debug_str(offending);
+    ystr_build(&fullmsg, "%s: %s: %s", ybuiltin_name(self), ystr_str(&msg),
+               ystr_str(&expr));
+
+    yexpr_t exception = yexpr_new();
+    yexpr_set_exception(&exception, fullmsg);
+    ystr_destroy(&expr);
+    ystr_destroy(&msg);
+    return exception;
+}
+
+static YBUILTIN_TYPE ybuiltin_id(ystr_t* sym) {
+    const size_t builtins =
+        sizeof(YBUILTIN_ID_MAPPING) / sizeof(struct ybuiltin_id_mapping);
+    for (size_t i = 0; i < builtins; i++) {
+        if (0 == ystr_cmp_str(sym, YBUILTIN_ID_MAPPING[i].id)) {
+            return YBUILTIN_ID_MAPPING[i].builtin;
+        }
+    }
+    return YBUILTIN_UNDEF;
+}
+
+static void nothing(void){};
+
+/// Expects a reference and a value on the stack. They may not depend on other
+/// references except for functions, i.e. should have been copied using
+/// `yexpr_copy()` (which is the convention for calling functions anyway).
+///
+/// Returns an UNDEF expression by default, or an EXCEPTION if something went
+/// wrong.
+yexpr_t ybuiltin_fn_let(yvec_t* call_stack) {
+    yexpr_t ref, val;
+    if (!yvec_pop(call_stack, &val)) goto notenoughfail;
+    if (!yvec_pop(call_stack, &ref)) goto notenoughfail;
+
+    if (ref.typ != YEXPR_REF || val.typ == YEXPR_UNDEF) goto typefail;
+    yvalue_t newvalue = {.typ = YVALUE_EXPR, .value.expr = val};
+    yvalue_set(ref.value.ref, &newvalue);
+    return yexpr_new();
+notenoughfail:
+    return ybuiltin_type_error(
+        YBUILTIN_LET, ystr_new("BUG: not enough arguments on call stack"),
+        NULL);
+typefail:
+    nothing();
+    yexpr_t args = yexpr_new();
+    yexpr_init_or_extend(&args, ref);
+    yexpr_init_or_extend(&args, val);
+    yexpr_t exc = ybuiltin_type_error(
+        YBUILTIN_LET,
+        ystr_new(
+            "type mismatch: want (reference, value), received something else"),
+        &args);
+    yexpr_destroy(&args);
+    return exc;
+}
+
 /**
  * @brief Table of built-in functions, indexed by YBUILTIN_TYPE.
  *
  * TODO: write built-ins.
  */
-const ybuiltin_fn YBUILTIN_FNS[0];
-
+static const ybuiltin_fn YBUILTIN_FNS[] = {NULL, NULL, ybuiltin_fn_let};
 
 const char* ybuiltin_name(YBUILTIN_TYPE t) {
     assert(t * sizeof(const char*) < sizeof(YBUILTIN_ENUM_STR));
@@ -27,6 +103,7 @@
 }
 
 yexpr_t ybuiltin_run(YBUILTIN_TYPE t, yvec_t* call_stack) {
-    assert(t * sizeof(ybuiltin_fn) < sizeof(YBUILTIN_FNS));
+    assert((t * sizeof(ybuiltin_fn)) < sizeof(YBUILTIN_FNS));
     return YBUILTIN_FNS[t](call_stack);
 }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/built-ins_test.c	Sun Aug 25 18:46:25 2019 +0200
@@ -0,0 +1,60 @@
+#include "built-ins.h"
+
+#include <src/atom.h>
+#include <src/value.h>
+
+void test_builtin_let(void) {
+    yexpr_t ref_expr = yexpr_new();
+    yref_t ref = yref_new();
+    yexpr_set_ref(&ref_expr, ref);
+    yexpr_t val_expr = yexpr_new();
+    yexpr_set_atom(&val_expr, yatom_get_or_add("my-atom"));
+
+    // NOTE: The expressions should be copied, but for this test we can store
+    // them directly.
+    yvec_t stack;
+    YVEC_INIT(&stack, 4, yexpr_t);
+    YVEC_PUSH(&stack, &ref_expr);
+    YVEC_PUSH(&stack, &val_expr);
+
+    yexpr_t result = ybuiltin_run(YBUILTIN_LET, &stack);
+    yexpr_debug(&result);
+    fputs("\n", stderr);
+    assert(result.typ == YEXPR_UNDEF);
+    assert(stack.len == 0);
+
+    yvalue_t* stored = yvalue_get(ref);
+    assert(stored->typ == YVALUE_EXPR);
+    assert(stored->value.expr.typ == YEXPR_ATOM);
+    assert(stored->value.expr.value.atom == val_expr.value.atom);
+
+    // Type mismatch exception.
+    yexpr_set_number(&ref_expr, 124);
+    YVEC_PUSH(&stack, &ref_expr);
+    YVEC_PUSH(&stack, &val_expr);
+    result = ybuiltin_run(YBUILTIN_LET, &stack);
+    yexpr_debug(&result);
+    fputs("\n", stderr);
+    assert(result.typ == YEXPR_EXCEPTION);
+    assert(stack.len == 0);
+    yexpr_destroy(&result);
+
+    // Not enough arguments on stack.
+    result = ybuiltin_run(YBUILTIN_LET, &stack);
+    assert(result.typ == YEXPR_EXCEPTION);
+    fputs(ystr_str(&result.value.str), stderr);
+    assert(0 ==
+           ystr_cmp_str(
+               &result.value.str,
+               "BUILTIN:LET: BUG: not enough arguments on call stack: NULL"));
+
+    yexpr_destroy(&result);
+    yexpr_destroy(&ref_expr);
+    yexpr_destroy(&val_expr);
+    yvec_destroy(&stack);
+};
+
+int main(void) {
+    test_builtin_let();
+    return 0;
+}
--- a/src/types.h	Sun Aug 25 18:46:06 2019 +0200
+++ b/src/types.h	Sun Aug 25 18:46:25 2019 +0200
@@ -118,7 +118,6 @@
  */
 typedef yexpr_t (*ybuiltin_fn)(yvec_t* call_stack);
 
-
 /**
  * @}
  */