Repository: SimuCorps/Gem
Pull request #11: Feat/time: Time module for stl
Author: login: DevYatsu
Created: 2025-05-27T11:06:05Z | Merged: 2025-05-30T23:09:24Z

Hey!

I’ve been experimenting with a basic time module, and while the goal was to build something practical, it also gave me the opportunity to reflect on some of the key features that I believe could significantly enhance the language at this stage of development.

This pull request is far from production-ready, it’s more of a conceptual proposal and discussion starter.

**Key Observations and Suggestions**

1) Support for typed class arguments or instanceof
One of the most urgent features, in my opinion, is the ability to either:
	•	enforce type-safe method arguments within classes (preferably), or
	•	introduce something like JavaScript’s instanceof.
For example, in the Duration class, being able to write methods that safely accept another Duration instance would improve clarity and eliminate the need for manual runtime type checks — which feels at odds with the statically typed nature of the language.

2) Proper error handling
This is foundational. Right now, functions like elapsed() have to return nil on invalid input, but ideally, we’d want a way to raise and handle meaningful errors. I probably don’t need to justify this one 😄

3) Static methods in classes
Many languages support static methods for two main reasons:
	•	Alternate constructors (from_seconds, from_millis, etc.)
	•	Utility functions related to a class, but not tied to instance data
While the first use case can often be handled via class inheritance, the second could bring real value. It’s not strictly necessary, but would definitely improve ergonomics.

4) Operator overloading for class types
Adding support for defining basic arithmetic and comparison operators would avoid a lot of boilerplate in classes like Duration, and make code involving them far more expressive and natural.

Looking forward to your thoughts! Just let me know which direction or feature you’d like me to focus on.

Thank you for work !


Diff:
diff --git a/src/vm.c b/src/vm.c
index c33351a..4448420 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -9,6 +9,7 @@
 //< Strings vm-include-string
 //> Calls and Functions vm-include-time
 #include <time.h>
+#include <sys/time.h>
 //< Calls and Functions vm-include-time
 #include <math.h>
 #include <stdlib.h>
@@ -1208,6 +1209,59 @@ static Value httpDeleteWithOptionsNative(int argCount, Value* args) {
   return OBJ_VAL(responseHash);
 }
 
+//> TIME Native Functions
+
+// Returns the current Unix epoch time (seconds since 1970-01-01T00:00:00 UTC).
+// Includes microsecond precision.
+static Value epochClockNative(int argCount, Value* args) {
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  const double now = (double)tv.tv_sec + (double)tv.tv_usec / CLOCKS_PER_SEC;
+  return NUMBER_VAL(now);
+}
+
+//< For using sleep functions
+#if _WIN32
+#include <windows.h>
+// Windows Sleep takes milliseconds
+#define sleep_ms(ms) Sleep((DWORD)(ms));
+#else
+#include <unistd.h>
+// Unix usleep takes nanoseconds
+#define sleep_ms(ms) usleep((useconds_t)ms * 1e3);
+#endif
+//>
+
+// Set execution to sleep for a specified number of milliseconds.
+static Value sleepNative(int argCount, Value* args) {
+  if (argCount != 1) {
+    runtimeError("sleep() takes 1 argument: time (in milliseconds)");
+    return NIL_VAL;
+  }
+
+  if (!IS_NUMBER(args[0])) {
+    runtimeError("sleep() first argument must be a positive time int");
+    return NIL_VAL;
+  }
+
+  const double milliseconds = AS_NUMBER(args[0]);
+
+  if (milliseconds < 0) {
+    runtimeError("sleep() first argument must be a positive number");
+    return NIL_VAL;
+  }
+
+  if (milliseconds > UINT_MAX) {
+    runtimeError("sleep() first argument must not be bigger than 4294967295U");
+    return NIL_VAL;
+  }
+
+  sleep_ms(milliseconds);
+
+  return NIL_VAL;
+}
+//< TIME Native Functions
+
 void initVM() {
 #if FAST_STACK_ENABLED
   // Fast stack is pre-allocated, just reset the pointer
@@ -1253,6 +1307,11 @@ void initVM() {
   defineNative("httpPutWithOptions", httpPutWithOptionsNative);
   defineNative("httpDeleteWithOptions", httpDeleteWithOptionsNative);
 //< HTTP Native Functions define
+  //> TIME Native Functions define
+  defineNative("epochClock", epochClockNative);
+  defineNative("sleepMs", sleepNative);
+  //< TIME Native Functions define
+
 //> Initialize Compiler Tables
   initCompilerTables();
 //< Initialize Compiler Tables
diff --git a/stl/time.gem b/stl/time.gem
new file mode 100644
index 0000000..5b7da8e
--- /dev/null
+++ b/stl/time.gem
@@ -0,0 +1,97 @@
+module Time
+  def now() int
+    return epochClock();
+  end
+
+  def elapsed(int since) int?
+    int current = Time.now();
+    # should raise an error instead of returning int?
+    if (since > current)
+        return nil;
+    end
+    return current - since;
+  end
+
+  def sleep(int seconds) void
+    sleepMs(seconds);
+  end
+
+  def measure(func f) int?
+    int start = Time.now();
+    f();
+    return Time.elapsed(start);
+  end
+
+  def timeout(func f, int maxSeconds) bool
+    return Time.measure(f) <= maxSeconds;
+  end
+end
+
+class Duration
+  def init(int milliseconds) void
+      if (milliseconds < 0)
+        this.milliseconds = 0;
+        return;
+      end
+      this.milliseconds = milliseconds;
+  end
+
+  # ACCESSORS
+  def as_millis() int
+      return this.milliseconds;
+  end
+
+  def as_seconds() int
+      return this.milliseconds / 1000;
+  end
+
+  def as_minutes() int
+      return this.as_seconds() / 60;
+  end
+
+  # OPERATIONS
+  def add(obj other) obj
+      if (other.milliseconds)
+        return Duration(this.milliseconds + other.milliseconds);
+      else
+        return this;
+      end
+  end
+
+  def sub(obj other) obj
+      if (other.milliseconds)
+        if (this.milliseconds - other.milliseconds < 0)
+          return Duration(0)
+        else
+          return Duration(this.milliseconds - other.milliseconds);
+        end
+      else
+        return this;
+      end
+  end
+
+  def mul(int factor) obj
+      return Duration(this.milliseconds * factor);
+  end
+
+  def div(int divisor) obj
+      return Duration(this.milliseconds / divisor);
+  end
+
+  # Comparisons
+  def gt(obj other) bool
+      return this.milliseconds > other.milliseconds;
+  end
+
+  def lt(obj other) bool
+      return this.milliseconds < other.milliseconds;
+  end
+
+  def eq(obj other) bool
+      return this.milliseconds == other.milliseconds;
+  end
+
+  def to_string() string
+      return this.as_seconds().to_string() + "s";
+  end
+end
\ No newline at end of file


Reviews:
user: login: Dishpit
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T19:30:16Z
user: login: Dishpit
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T22:45:20Z
user: login: Dishpit
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T23:09:16Z

Review comments:
diff_hunk: @@ -0,0 +1,105 @@
+module TIME
path: stl/time.gem
user: login: Dishpit
body: can we get this renamed `Time` to maintain consistency with `Math`? (`HTTP` is only capitalized due to it being an acronym)
created_at: 2025-05-30T19:29:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+module TIME
+    def now() int
path: stl/time.gem
user: login: Dishpit
body: only other thing: can we shift this down to 2-space tabs?
created_at: 2025-05-30T19:30:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: Dishpit
created_at: 2025-05-30T19:33:26Z
body: i actually really like this! it's also a great foundational start to other libs in the future such as random numbers and cryptography, so it's a _really_ solid addition to the codebase

just see the other comments re: formatting and i'll get this merged; static functions et al. can be added later and then this can be built out even further after that point. i'd say it's about as production-ready as it can be, given the current state of the language
author_association: CONTRIBUTOR
user: login: Dishpit
created_at: 2025-05-30T19:36:15Z
body: ah, the build CI's failing
author_association: CONTRIBUTOR
user: login: DevYatsu
created_at: 2025-05-30T22:26:36Z
body: Let me check that out !
author_association: NONE
user: login: DevYatsu
created_at: 2025-05-30T22:46:46Z
body: Wait, not done yet haha
author_association: NONE
user: login: DevYatsu
created_at: 2025-05-30T22:59:53Z
body: I believe it should be fixed now
author_association: NONE
user: login: DevYatsu
created_at: 2025-05-30T23:01:56Z
body: Okay, just moving an include statement out of a function and it's done :0
author_association: NONE