hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 5fc0a4ff6b7700edd35692f605056afa28aecbdf
parent bab9aefa2f6682aa4a850cde6f3f465a8cf21ef0
Author: Byron Torres <b@torresjrjr.com>
Date:   Sat, 12 Mar 2022 16:25:52 +0000

new chrono::isocal:: modules

Signed-off-by: Byron Torres <b@torresjrjr.com>

Diffstat:
Achrono/README | 4++++
Achrono/calendar.ha | 16++++++++++++++++
Achrono/chronology.ha | 17+++++++++++++++++
Achrono/isocal/README | 5+++++
Achrono/isocal/datetime.ha | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Achrono/isocal/types.ha | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Achrono/timescales.ha | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 18++++++++++++++++++
Mstdlib.mk | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtime/+linux/functions.ha | 12++++++------
Atime/README | 4++++
Mtime/arithm.ha | 39+++++++++++++++++++++++++++++++++++----
Atime/conv.ha | 8++++++++
Mtime/types.ha | 29+++++++----------------------
14 files changed, 424 insertions(+), 32 deletions(-)

diff --git a/chrono/README b/chrono/README @@ -0,0 +1,4 @@ +The chrono module provides the basis for chronology, namely timescales +and leap seconds, and abstracted interfaces for calendars and datetimes. + +For working with the ISO calendar, see the [[chrono::isocal]] submodule. diff --git a/chrono/calendar.ha b/chrono/calendar.ha @@ -0,0 +1,16 @@ +use time; + +// Represents a system for ordering days as dates and eras. +// Allows conversion between [[date]] types. +export type calendar = struct { + // TODO + TODO: bool, +}; + +export type localdate = struct { + nday: i64, +}; + +export type localtime = struct { + ntime: time::duration, +}; diff --git a/chrono/chronology.ha b/chrono/chronology.ha @@ -0,0 +1,17 @@ +use time; + +// Represents a [[timescale]]-[[calendar]] system for ordering events. +// Allows conversion between [[datetime]] and [[time::instant]] types. +// +// Notes: +// Needs rethinking. Initially, the idea was to have the possibility of multiple +// plug-and-plug calendars, but since hare doesn't have objects, we need to +// deisgn this another way. Probably, datetime:: will be solely ISO 8601 +// Proleptic Gregorian, with TAI, UTC, and UNIX, and then we'll have something +// like datetime::chrono::* for alternative chronologies. 3rd party libraries +// would be similar. +export type chronology = struct { + timescale: timescale, + calendar: calendar, +}; + diff --git a/chrono/isocal/README b/chrono/isocal/README @@ -0,0 +1,5 @@ +The chrono::isocal submodule implements the international standard +proleptic Gregorian calendar, as per ISO 8601. + +This module provides datetimes, timezones, offsets, and the arithmetics, +formatting, parsing, serialization, and conversion thereof. diff --git a/chrono/isocal/datetime.ha b/chrono/isocal/datetime.ha @@ -0,0 +1,73 @@ +use time; + +// Creates a new datetime, using a [[calendardate]]. +// +// // 1995 July 18th 09:16:00.000 +// datetime::new(1995, 07, 18, 9, 16, 0, 0, datetime::local) +// chrono::iso::new(1995, 07, 18, 9, 16, 0, 0, chrono::iso::local) +// +// For alternative forms, assemble a datetime manually using the desired types. +export fn new( + year: i64, + month: u8, + day: u8, + hour: u8, + min: u8, + sec: u8, + nsec: u64, + loc: locality, +) datetime = datetime { + date = localdate { + year = year, + month = month, + day = day, + }, + time = localtime { + hour = hour, + min = min, + sec = sec, + nsec = nsec, + }, + loc = loc, +}; + +// Returns the current datetime +export fn now() datetime = { + const i = time::now(time::clock::REALTIME); + const u = time::unix(i); + const dt = datetime { + date = localdate { + // TODO: figure out calendar arithmetics first + year = 1970, + month = 1, + day = (i.sec / 86400): u8, + }, + time = localtime { + hour = (i.sec / 3600): u8 % 24, + min = (i.sec / 60): u8 % 60, + sec = i.sec: u8 % 60, + nsec = i.nsec: u64, + }, + + // TODO: What to do here? How to get the timezone from + // /etc/localtime? How to determine the system's timescale? + // Assuming UTC may be sufficient. + loc = local, + }; + return dt; +}; + +// Normalizes all fields a datetime +export fn normalize(dt: datetime) datetime = { + validate(dt)!; + // TODO + return dt; +}; + +// Validates a datetime +export fn validate(dt: datetime) (datetime | error) = { + // TODO + return dt; +}; + +export type error = !void; diff --git a/chrono/isocal/types.ha b/chrono/isocal/types.ha @@ -0,0 +1,79 @@ +use time; +use chrono; + +// Represents a ISO datetime +// +// Notes: +// Java has good separation of types: A LocalDatetime, ZonedDatetime, +// OffsetDatetime. Python instead reasons about datetimes as being +// timezone-aware/naive. Here's I try to leaverage Hare's type system to combine +// the two. +// +// Putting the date and time related fields into separate typed structs maybe +// isn't a good idea (see `type localdate` below), but I still put it here +// because localtime is often used on it's own, and it makes some sense to have +// a datetime be composed of a date and time. +export type datetime = struct { + date: localdate, + time: localtime, + loc: locality, +}; + +// Represents a ISO date +export type localdate = struct { + // days since calendar epoch + epochday: i64, + + // ISO calendar date + year: i64, + month: u8, + day: u8, + + // ISO week date + weekyear: i64, + week: u8, + weekday: u8, + + // ISO ordinal date + yearday: uint, +}; + +// Represents a ISO time of day +export type localtime = struct { + hour: u8, + min: u8, + sec: u8, + nsec: u64, +}; + +// Represents the locality of a datetime +// +// Notes: +// `timescale` may not meaningfully belong here. It's here because sometimes ISO +// datetimes like "1999-05-13T12:30:45.125 UTC" exist as a human-readible form +// of a UTC instant, using the ISO calendar, and where localtime.sec ranges only +// from 0-59. +export type locality = (local | zoneoffset | timezone); + +// Represents its associated datetime as local +export type local = void; + +// Represents a simple, constant offset +// +// Notes: +// Should this be "utcoffset"? +// Should this just be seconds? +export type zoneoffset = time::duration; + +// Represents a timezone; a political region with a ruleset regarding offsets +// +// Notes: +// timezone types design needs a lot of thought. Enum is probably not a good +// idea. Initially this was a `type enum str {}`, which may have been a worse +// idea. Maybe we need a `fn tzdb(tz: str)` like other langs' libs. +export type timezone = enum { + UTC, + EUROPE_LONDON, + // ... +}; + diff --git a/chrono/timescales.ha b/chrono/timescales.ha @@ -0,0 +1,80 @@ +use time; + +// Represents a linear scale of time, with an epoch. +export type timescale = struct { + to_tai: *fn(i: time::instant) []time::instant, + from_tai: *fn(i: time::instant) []time::instant, +}; + + +// International Atomic Time +// +// The realisation of proper time on Earth's geoid. +// Continuous (no leap seconds). +export const TAI: timescale = timescale { + to_tai = &conv_tai_tai, + from_tai = &conv_tai_tai, +}; + +fn conv_tai_tai(i: time::instant) []time::instant = { + return [i]; +}; + + +// TODO: Write proper conversion functions for all timescales. +// Functions are to return two or no instants, depending on any leap second +// events, and use a proper leap second table. +// https://www.ietf.org/timezones/data/leap-seconds.list + + +// Coordinated Universal Time +// +// Based on TAI. Used as the basis of civil timekeeping. +// Discontinuous (has leap seconds). +export const UTC: timescale = timescale { + to_tai = &conv_utc_tai, + from_tai = &conv_tai_utc, +}; + +fn conv_tai_utc(tai: time::instant) []time::instant = { + const utc = time::instant { + sec = tai.sec - 37, + nsec = tai.nsec, + }; + return [utc]; +}; + +fn conv_utc_tai(utc: time::instant) []time::instant = { + const tai = time::instant { + sec = utc.sec + 37, + nsec = utc.nsec, + }; + return [tai]; +}; + + +// Unix Time +// +// Based on UTC. Used for computer timekeeping. +// Discontinuous (has leap seconds). +export const UNIX: timescale = timescale { + to_tai = &conv_utc_tai, + from_tai = &conv_tai_utc, +}; + +fn conv_tai_unix(tai: time::instant) []time::instant = { + const unix = time::instant { + sec = tai.sec - 37, + nsec = tai.nsec, + }; + return [unix]; +}; + +fn conv_unix_tai(unix: time::instant) []time::instant = { + const tai = time::instant { + sec = unix.sec + 37, + nsec = unix.nsec, + }; + return [tai]; +}; + diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -181,6 +181,21 @@ bytes() { gen_ssa bytes types } +chrono() { + gen_srcs chrono \ + chronology.ha \ + timescales.ha \ + calendar.ha + gen_ssa chrono time +} + +chrono_isocal() { + gen_srcs chrono::isocal \ + datetime.ha \ + types.ha + gen_ssa chrono::isocal chrono time +} + compress_flate() { gen_srcs compress::flate \ inflate.ha @@ -1153,6 +1168,7 @@ time() { +linux/functions.ha \ +linux/+'$(ARCH)'.ha \ arithm.ha \ + conv.ha \ types.ha gen_ssa -plinux time linux::vdso gen_srcs -pfreebsd time \ @@ -1253,6 +1269,8 @@ uuid() { modules="ascii bufio bytes +chrono +chrono::isocal compress::flate compress::zlib crypto diff --git a/stdlib.mk b/stdlib.mk @@ -148,6 +148,18 @@ stdlib_deps_any+=$(stdlib_bytes_any) stdlib_bytes_linux=$(stdlib_bytes_any) stdlib_bytes_freebsd=$(stdlib_bytes_any) +# gen_lib chrono (any) +stdlib_chrono_any=$(HARECACHE)/chrono/chrono-any.o +stdlib_deps_any+=$(stdlib_chrono_any) +stdlib_chrono_linux=$(stdlib_chrono_any) +stdlib_chrono_freebsd=$(stdlib_chrono_any) + +# gen_lib chrono::isocal (any) +stdlib_chrono_isocal_any=$(HARECACHE)/chrono/isocal/chrono_isocal-any.o +stdlib_deps_any+=$(stdlib_chrono_isocal_any) +stdlib_chrono_isocal_linux=$(stdlib_chrono_isocal_any) +stdlib_chrono_isocal_freebsd=$(stdlib_chrono_isocal_any) + # gen_lib compress::flate (any) stdlib_compress_flate_any=$(HARECACHE)/compress/flate/compress_flate-any.o stdlib_deps_any+=$(stdlib_compress_flate_any) @@ -707,6 +719,29 @@ $(HARECACHE)/bytes/bytes-any.ssa: $(stdlib_bytes_any_srcs) $(stdlib_rt) $(stdlib @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nbytes \ -t$(HARECACHE)/bytes/bytes.td $(stdlib_bytes_any_srcs) +# chrono (+any) +stdlib_chrono_any_srcs= \ + $(STDLIB)/chrono/chronology.ha \ + $(STDLIB)/chrono/timescales.ha \ + $(STDLIB)/chrono/calendar.ha + +$(HARECACHE)/chrono/chrono-any.ssa: $(stdlib_chrono_any_srcs) $(stdlib_rt) $(stdlib_time_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/chrono + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nchrono \ + -t$(HARECACHE)/chrono/chrono.td $(stdlib_chrono_any_srcs) + +# chrono::isocal (+any) +stdlib_chrono_isocal_any_srcs= \ + $(STDLIB)/chrono/isocal/datetime.ha \ + $(STDLIB)/chrono/isocal/types.ha + +$(HARECACHE)/chrono/isocal/chrono_isocal-any.ssa: $(stdlib_chrono_isocal_any_srcs) $(stdlib_rt) $(stdlib_chrono_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/chrono/isocal + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nchrono::isocal \ + -t$(HARECACHE)/chrono/isocal/chrono_isocal.td $(stdlib_chrono_isocal_any_srcs) + # compress::flate (+any) stdlib_compress_flate_any_srcs= \ $(STDLIB)/compress/flate/inflate.ha @@ -1739,6 +1774,7 @@ stdlib_time_linux_srcs= \ $(STDLIB)/time/+linux/functions.ha \ $(STDLIB)/time/+linux/+$(ARCH).ha \ $(STDLIB)/time/arithm.ha \ + $(STDLIB)/time/conv.ha \ $(STDLIB)/time/types.ha $(HARECACHE)/time/time-linux.ssa: $(stdlib_time_linux_srcs) $(stdlib_rt) $(stdlib_linux_vdso_$(PLATFORM)) @@ -2054,6 +2090,18 @@ testlib_deps_any+=$(testlib_bytes_any) testlib_bytes_linux=$(testlib_bytes_any) testlib_bytes_freebsd=$(testlib_bytes_any) +# gen_lib chrono (any) +testlib_chrono_any=$(TESTCACHE)/chrono/chrono-any.o +testlib_deps_any+=$(testlib_chrono_any) +testlib_chrono_linux=$(testlib_chrono_any) +testlib_chrono_freebsd=$(testlib_chrono_any) + +# gen_lib chrono::isocal (any) +testlib_chrono_isocal_any=$(TESTCACHE)/chrono/isocal/chrono_isocal-any.o +testlib_deps_any+=$(testlib_chrono_isocal_any) +testlib_chrono_isocal_linux=$(testlib_chrono_isocal_any) +testlib_chrono_isocal_freebsd=$(testlib_chrono_isocal_any) + # gen_lib compress::flate (any) testlib_compress_flate_any=$(TESTCACHE)/compress/flate/compress_flate-any.o testlib_deps_any+=$(testlib_compress_flate_any) @@ -2613,6 +2661,29 @@ $(TESTCACHE)/bytes/bytes-any.ssa: $(testlib_bytes_any_srcs) $(testlib_rt) $(test @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nbytes \ -t$(TESTCACHE)/bytes/bytes.td $(testlib_bytes_any_srcs) +# chrono (+any) +testlib_chrono_any_srcs= \ + $(STDLIB)/chrono/chronology.ha \ + $(STDLIB)/chrono/timescales.ha \ + $(STDLIB)/chrono/calendar.ha + +$(TESTCACHE)/chrono/chrono-any.ssa: $(testlib_chrono_any_srcs) $(testlib_rt) $(testlib_time_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/chrono + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nchrono \ + -t$(TESTCACHE)/chrono/chrono.td $(testlib_chrono_any_srcs) + +# chrono::isocal (+any) +testlib_chrono_isocal_any_srcs= \ + $(STDLIB)/chrono/isocal/datetime.ha \ + $(STDLIB)/chrono/isocal/types.ha + +$(TESTCACHE)/chrono/isocal/chrono_isocal-any.ssa: $(testlib_chrono_isocal_any_srcs) $(testlib_rt) $(testlib_chrono_$(PLATFORM)) $(testlib_time_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/chrono/isocal + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nchrono::isocal \ + -t$(TESTCACHE)/chrono/isocal/chrono_isocal.td $(testlib_chrono_isocal_any_srcs) + # compress::flate (+any) testlib_compress_flate_any_srcs= \ $(STDLIB)/compress/flate/inflate.ha @@ -3690,6 +3761,7 @@ testlib_time_linux_srcs= \ $(STDLIB)/time/+linux/functions.ha \ $(STDLIB)/time/+linux/+$(ARCH).ha \ $(STDLIB)/time/arithm.ha \ + $(STDLIB)/time/conv.ha \ $(STDLIB)/time/types.ha $(TESTCACHE)/time/time-linux.ssa: $(testlib_time_linux_srcs) $(testlib_rt) $(testlib_linux_vdso_$(PLATFORM)) diff --git a/time/+linux/functions.ha b/time/+linux/functions.ha @@ -7,15 +7,15 @@ use linux::vdso; // Converts a [[duration]] to an [[rt::timespec]]. This function is // non-portable. -export fn duration_to_timespec(n: duration, ts: *rt::timespec) void = { - ts.tv_sec = n / SECOND; - ts.tv_nsec = n % SECOND; +export fn duration_to_timespec(d: duration, ts: *rt::timespec) void = { + ts.tv_sec = d / SECOND; + ts.tv_nsec = d % SECOND; }; // Converts an [[instant]] to an [[rt::timespec]]. This function is // non-portable. export fn instant_to_timespec(t: instant, ts: *rt::timespec) void = { - ts.tv_sec = t.sec; + ts.tv_sec = t.sec; ts.tv_nsec = t.nsec; }; @@ -27,9 +27,9 @@ export fn timespec_to_instant(ts: rt::timespec) instant = instant { }; // Yields the process to the kernel and returns after the requested duration. -export fn sleep(n: duration) void = { +export fn sleep(d: duration) void = { let in = rt::timespec { ... }; - duration_to_timespec(n, &in); + duration_to_timespec(d, &in); let req = &in; for (true) { diff --git a/time/README b/time/README @@ -0,0 +1,4 @@ +The time module provides basic timekeeping primitives and access to the +system's clocks. + +For more human abstractions of time, see the [[chrono]] module. diff --git a/time/arithm.ha b/time/arithm.ha @@ -1,4 +1,5 @@ // License: MPL-2.0 +// (c) 2022 Byron Torres <b@torresjrjr.com> // (c) 2021 Drew DeVault <sir@cmpwn.com> // Adds a [[duration]] to an [[instant]], returning an instant further in the @@ -9,10 +10,40 @@ export fn add(a: instant, d: duration) instant = instant { nsec = a.nsec + d, }; -// Returns the duration from [[instant]] a to [[instant]] b. -// If a is earlier than b, the duration is positive. -// If a is equivilent to b, the duration is zero. -// If a is later than b, the duration is negative. +// Returns the [[duration]] from [[instant]] "a" to [[instant]] "b". export fn diff(a: instant, b: instant) duration = { return ((b.sec - a.sec) * SECOND) + (b.nsec - a.nsec); }; + +// Returns: +// -1 if a precedes b; +// 0 if a and b are simultaneous; +// +1 if b precedes a; +// +// Notes: +// At first seems superceded by elapsed, but useful when you need values at +// compile time: +// switch (compare(a, b)) { +// case -1 => ...; +// case 0 => ...; +// case 1 => ...; +// case => abort("Unreachable") +// }; +// +// Would it be better as an enum, to avoid that abort()? +export fn compare(a: instant, b: instant) i8 = { + return if (a.sec < b.sec) -1 + else if (a.sec > b.sec) 1 + else if (a.nsec < b.nsec) -1 + else if (a.nsec > b.nsec) 1 + else 0; +}; + +@test fn compare() void = { + let a = now(clock::MONOTONIC); + sleep(1 * MILLISECOND); + let b = now(clock::MONOTONIC); + assert(compare(a, b) < 0); + assert(compare(b, a) > 0); + assert(compare(a, a) == 0); +}; diff --git a/time/conv.ha b/time/conv.ha @@ -0,0 +1,8 @@ +// Converts the given [[instant]] to a Unix timestamp. +export fn unix(a: instant) i64 = a.sec; + +// Returns a [[instant]] from a Unix timestamp. +export fn from_unix(u: i64) instant = instant { + sec = u, + nsec = 0, +}; diff --git a/time/types.ha b/time/types.ha @@ -27,28 +27,13 @@ export def HOUR: duration = 60 * MINUTE; // Represents a specific instant in time as seconds (+nanoseconds) since an // arbitrary epoch. Instants may only be meaningfully compared with other // instants sourced from the same clock. -export type instant = struct { sec: i64, nsec: i64 }; - -// The return value is negative if a < b, zero if a == b, and positive if a > b. -export fn compare(a: instant, b: instant) int = { - return if (a.sec < b.sec) -1 else if (a.sec > b.sec) 1 else - if (a.nsec < b.nsec) -1 else if (a.nsec > b.nsec) 1 else 0; -}; - -// Converts the given [[instant]] to a Unix timestamp. -export fn unix(a: instant) i64 = a.sec; - -// Returns a [[instant]] from a Unix timestamp. -export fn from_unix(u: i64) instant = instant { - sec = u, - nsec = 0, +export type instant = struct { + sec: i64, + nsec: i64, }; -@test fn compare() void = { - let a = now(clock::MONOTONIC); - sleep(1 * MILLISECOND); - let b = now(clock::MONOTONIC); - assert(compare(a, b) < 0); - assert(compare(b, a) > 0); - assert(compare(a, a) == 0); +// Represents a unique interval of time between two instants. +export type interval = struct { + start: instant, + end: instant, };