add Calendar + CalendarTime + Rhythm + LocalDate/Time as first-class
Phase 1.5 of time-system. Calendar is pluggable: EarthCalendar (IANA zones, DST, Gregorian) is the default; MarsCalendar, CycleCalendar(period), NoCycleCalendar handle non-Earth cases. Rhythm abstracts recurrence from clock units - rhythm_cycle_phase(0.5) means "midpoint of cycle" whether the cycle is 24 hours on Earth or 30 hours on a station or 300 years on a long-cycle world. Phase 1 (Instant + Duration) unchanged. EarthCalendar(zone_local()) is the user-facing default; nobody who doesn't care about non-Earth calendars sees the abstraction. Self-host fixed point holds at 6339 lines. Snapshot tagged at dist/platform/elc.20260502-1321-self-host. Phase 2 (scheduling primitives every/after/at) lands next, now with Calendar-aware grounding instead of Earth-time hardcoded. Backlog: bl-297f66d8 (supersedes bl-b29b3e60)
This commit is contained in:
@@ -202,6 +202,51 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
||||
let right_is_inst: Bool = is_instant_expr(right)
|
||||
let left_is_dur: Bool = is_duration_expr(left)
|
||||
let right_is_dur: Bool = is_duration_expr(right)
|
||||
|
||||
// Phase 1.5 LocalDate / LocalTime / CalendarTime dispatch. These
|
||||
// route through their typed runtime wrappers (el_local_date_add_dur,
|
||||
// el_local_time_add_dur, el_local_date_lt, el_local_date_eq) and
|
||||
// forbid mismatched ops at codegen time. Cross-calendar arithmetic
|
||||
// (CalendarTime + CalendarTime, CalendarTime - CalendarTime under
|
||||
// mismatched calendars) is structurally meaningless: a CalendarTime
|
||||
// already projects an Instant under a Calendar, so subtraction
|
||||
// between two of them only makes sense in instant-space (use
|
||||
// cal_to_instant first).
|
||||
let left_is_ld: Bool = is_localdate_expr(left)
|
||||
let right_is_ld: Bool = is_localdate_expr(right)
|
||||
let left_is_lt: Bool = is_localtime_expr(left)
|
||||
let right_is_lt: Bool = is_localtime_expr(right)
|
||||
let left_is_ct: Bool = is_caltime_expr(left)
|
||||
let right_is_ct: Bool = is_caltime_expr(right)
|
||||
if left_is_ld {
|
||||
if op == "Plus" {
|
||||
if right_is_dur {
|
||||
return "el_local_date_add_dur(" + left_c + ", " + right_c + ")"
|
||||
}
|
||||
}
|
||||
if op == "Lt" {
|
||||
if right_is_ld { return "el_local_date_lt(" + left_c + ", " + right_c + ")" }
|
||||
}
|
||||
if op == "EqEq" {
|
||||
if right_is_ld { return "el_local_date_eq(" + left_c + ", " + right_c + ")" }
|
||||
}
|
||||
}
|
||||
if left_is_lt {
|
||||
if op == "Plus" {
|
||||
if right_is_dur {
|
||||
return "el_local_time_add_dur(" + left_c + ", " + right_c + ")"
|
||||
}
|
||||
}
|
||||
}
|
||||
if left_is_ct {
|
||||
if op == "Plus" {
|
||||
if right_is_ct {
|
||||
time_record_violation("caltime_plus_caltime", "CalendarTime + CalendarTime is not allowed (use cal_to_instant + Duration)")
|
||||
return "0 /* TIME_TYPE_ERROR: CalendarTime + CalendarTime */"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let any_temporal: Bool = false
|
||||
if left_is_inst { let any_temporal = true }
|
||||
if right_is_inst { let any_temporal = true }
|
||||
@@ -877,6 +922,27 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
|
||||
if str_eq(ltype, "Duration") {
|
||||
add_duration_name(name)
|
||||
}
|
||||
if str_eq(ltype, "Calendar") {
|
||||
add_calendar_name(name)
|
||||
}
|
||||
if str_eq(ltype, "CalendarTime") {
|
||||
add_caltime_name(name)
|
||||
}
|
||||
if str_eq(ltype, "Rhythm") {
|
||||
add_rhythm_name(name)
|
||||
}
|
||||
if str_eq(ltype, "LocalDate") {
|
||||
add_localdate_name(name)
|
||||
}
|
||||
if str_eq(ltype, "LocalTime") {
|
||||
add_localtime_name(name)
|
||||
}
|
||||
if str_eq(ltype, "LocalDateTime") {
|
||||
add_localdt_name(name)
|
||||
}
|
||||
if str_eq(ltype, "Zone") {
|
||||
add_zone_name(name)
|
||||
}
|
||||
// Inference from RHS — duration literals and known-typed calls
|
||||
// propagate even when the let is unannotated.
|
||||
if is_instant_expr(val) {
|
||||
@@ -885,6 +951,27 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
|
||||
if is_duration_expr(val) {
|
||||
add_duration_name(name)
|
||||
}
|
||||
if is_calendar_expr(val) {
|
||||
add_calendar_name(name)
|
||||
}
|
||||
if is_caltime_expr(val) {
|
||||
add_caltime_name(name)
|
||||
}
|
||||
if is_rhythm_expr(val) {
|
||||
add_rhythm_name(name)
|
||||
}
|
||||
if is_localdate_expr(val) {
|
||||
add_localdate_name(name)
|
||||
}
|
||||
if is_localtime_expr(val) {
|
||||
add_localtime_name(name)
|
||||
}
|
||||
if is_localdt_expr(val) {
|
||||
add_localdt_name(name)
|
||||
}
|
||||
if is_zone_expr(val) {
|
||||
add_zone_name(name)
|
||||
}
|
||||
let vk: String = val["expr"]
|
||||
if str_eq(vk, "Int") {
|
||||
add_int_name(name)
|
||||
@@ -1232,6 +1319,191 @@ fn is_duration_call(call_expr: Map<String, Any>) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Phase 1.5 — Calendar / CalendarTime / Rhythm / LocalDate / LocalTime /
|
||||
// LocalDateTime / Zone are first-class boxed types. Each has its own name
|
||||
// set in process state, populated from typed `let` bindings and parameter
|
||||
// annotations. The BinOp dispatcher consults these to forbid mismatched
|
||||
// arithmetic (e.g. CalendarTime + CalendarTime, LocalDate < CalendarTime).
|
||||
fn is_calendar_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__calendar_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
fn is_caltime_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__caltime_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
fn is_rhythm_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__rhythm_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
fn is_localdate_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__localdate_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
fn is_localtime_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__localtime_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
fn is_localdt_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__localdt_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
fn is_zone_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__zone_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
// Calendar-returning builtins. earth_calendar / mars_calendar / cycle_calendar
|
||||
// / no_cycle_calendar / relative_calendar all box a calendar struct.
|
||||
fn is_calendar_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "earth_calendar") { return true }
|
||||
if str_eq(name, "earth_calendar_default") { return true }
|
||||
if str_eq(name, "mars_calendar") { return true }
|
||||
if str_eq(name, "cycle_calendar") { return true }
|
||||
if str_eq(name, "no_cycle_calendar") { return true }
|
||||
if str_eq(name, "relative_calendar") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// CalendarTime-returning builtins.
|
||||
fn is_caltime_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "now_in") { return true }
|
||||
if str_eq(name, "in_calendar") { return true }
|
||||
if str_eq(name, "cal_in") { return true }
|
||||
if str_eq(name, "zoned") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// Rhythm-returning builtins.
|
||||
fn is_rhythm_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "rhythm_cycle_start") { return true }
|
||||
if str_eq(name, "rhythm_cycle_phase") { return true }
|
||||
if str_eq(name, "rhythm_duration") { return true }
|
||||
if str_eq(name, "rhythm_session_start") { return true }
|
||||
if str_eq(name, "rhythm_event") { return true }
|
||||
if str_eq(name, "rhythm_and") { return true }
|
||||
if str_eq(name, "rhythm_or") { return true }
|
||||
if str_eq(name, "rhythm_weekday") { return true }
|
||||
if str_eq(name, "rhythm_weekly_at") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// LocalDate-returning builtins.
|
||||
fn is_localdate_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "local_date") { return true }
|
||||
if str_eq(name, "el_local_date_add_dur") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_localtime_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "local_time") { return true }
|
||||
if str_eq(name, "el_local_time_add_dur") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_localdt_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "local_datetime") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_zone_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "zone") { return true }
|
||||
if str_eq(name, "zone_utc") { return true }
|
||||
if str_eq(name, "zone_local") { return true }
|
||||
if str_eq(name, "zone_offset") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_calendar_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_calendar_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_calendar_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_caltime_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_caltime_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_caltime_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_rhythm_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_rhythm_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_rhythm_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_localdate_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_localdate_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_localdate_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_localtime_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_localtime_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_localtime_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_localdt_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_localdt_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_localdt_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_zone_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Ident") { return is_zone_name(expr["name"]) }
|
||||
if str_eq(k, "Call") { return is_zone_call(expr) }
|
||||
return false
|
||||
}
|
||||
|
||||
// Recursive type predicates for Instant / Duration. Mirror is_int_expr.
|
||||
// is_instant_expr / is_duration_expr return true only when the expression
|
||||
// is provably of that type at codegen time. Anything ambiguous returns
|
||||
@@ -1810,10 +2082,80 @@ fn add_duration_name(name: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_calendar_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__calendar_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__calendar_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_caltime_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__caltime_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__caltime_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_rhythm_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__rhythm_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__rhythm_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_localdate_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__localdate_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__localdate_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_localtime_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__localtime_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__localtime_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_localdt_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__localdt_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__localdt_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_zone_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__zone_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__zone_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
||||
state_set("__int_names", ",")
|
||||
state_set("__instant_names", ",")
|
||||
state_set("__duration_names", ",")
|
||||
state_set("__calendar_names", ",")
|
||||
state_set("__caltime_names", ",")
|
||||
state_set("__rhythm_names", ",")
|
||||
state_set("__localdate_names", ",")
|
||||
state_set("__localtime_names", ",")
|
||||
state_set("__localdt_names", ",")
|
||||
state_set("__zone_names", ",")
|
||||
let np: Int = native_list_len(params)
|
||||
let pi = 0
|
||||
while pi < np {
|
||||
@@ -1829,6 +2171,27 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
||||
if str_eq(ptype, "Duration") {
|
||||
add_duration_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "Calendar") {
|
||||
add_calendar_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "CalendarTime") {
|
||||
add_caltime_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "Rhythm") {
|
||||
add_rhythm_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "LocalDate") {
|
||||
add_localdate_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "LocalTime") {
|
||||
add_localtime_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "LocalDateTime") {
|
||||
add_localdt_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "Zone") {
|
||||
add_zone_name(pname)
|
||||
}
|
||||
let pi = pi + 1
|
||||
}
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user