ranger-compiler 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,219 @@
1
+
2
+ ; Portable calendar math on ISO-8601 date strings (YYYY-MM-DD).
3
+ ; No host Date, timezone, or locale — safe to compile to any Ranger target.
4
+
5
+ class DateParts {
6
+ def year:int 2000
7
+ def month:int 1
8
+ def day:int 1
9
+
10
+ static fn fromIso:DateParts (iso:string) {
11
+ def p (new DateParts)
12
+ p.year = (DateMath.parseIsoComponent(iso 0 4))
13
+ p.month = (DateMath.parseIsoComponent(iso 5 2))
14
+ p.day = (DateMath.parseIsoComponent(iso 8 2))
15
+ return p
16
+ }
17
+
18
+ fn toIso:string () {
19
+ return (DateMath.formatIso(this.year this.month this.day))
20
+ }
21
+ }
22
+
23
+ class DateMath {
24
+ static fn parseIsoComponent:int (iso:string start:int len:int) {
25
+ def out@(mutable):int 0
26
+ def i@(mutable):int 0
27
+ while (i < len) {
28
+ def ch (charAt iso (start + i))
29
+ out = (out * 10) + (ch - 48)
30
+ i = i + 1
31
+ }
32
+ return out
33
+ }
34
+
35
+ static fn intToString:string (n:int) {
36
+ if (n == 0) {
37
+ return "0"
38
+ }
39
+ def digits:[string]
40
+ def v@(mutable):int n
41
+ while (v > 0) {
42
+ def digit (v % 10)
43
+ push digits (DateMath.digitChar(digit))
44
+ v = (floor ((v - digit) / 10))
45
+ }
46
+ def out:string ""
47
+ def cnt (array_length digits)
48
+ def i@(mutable):int (cnt - 1)
49
+ while (i >= 0) {
50
+ out = out + (itemAt digits i)
51
+ i = i - 1
52
+ }
53
+ return out
54
+ }
55
+
56
+ static fn digitChar:string (d:int) {
57
+ if (d == 0) {
58
+ return "0"
59
+ }
60
+ if (d == 1) {
61
+ return "1"
62
+ }
63
+ if (d == 2) {
64
+ return "2"
65
+ }
66
+ if (d == 3) {
67
+ return "3"
68
+ }
69
+ if (d == 4) {
70
+ return "4"
71
+ }
72
+ if (d == 5) {
73
+ return "5"
74
+ }
75
+ if (d == 6) {
76
+ return "6"
77
+ }
78
+ if (d == 7) {
79
+ return "7"
80
+ }
81
+ if (d == 8) {
82
+ return "8"
83
+ }
84
+ return "9"
85
+ }
86
+
87
+ static fn pad2:string (n:int) {
88
+ if (n < 10) {
89
+ return ("0" + (DateMath.intToString(n)))
90
+ }
91
+ return (DateMath.intToString(n))
92
+ }
93
+
94
+ static fn formatIso:string (year:int month:int day:int) {
95
+ return ((DateMath.intToString(year)) + "-" + (DateMath.pad2(month)) + "-" + (DateMath.pad2(day)))
96
+ }
97
+
98
+ static fn isLeapYear:boolean (year:int) {
99
+ if ((year % 4) != 0) {
100
+ return false
101
+ }
102
+ if ((year % 100) != 0) {
103
+ return true
104
+ }
105
+ if ((year % 400) != 0) {
106
+ return false
107
+ }
108
+ return true
109
+ }
110
+
111
+ static fn daysInMonth:int (year:int month:int) {
112
+ if (month == 2) {
113
+ if ((DateMath.isLeapYear(year))) {
114
+ return 29
115
+ }
116
+ return 28
117
+ }
118
+ if (month == 4) {
119
+ return 30
120
+ }
121
+ if (month == 6) {
122
+ return 30
123
+ }
124
+ if (month == 9) {
125
+ return 30
126
+ }
127
+ if (month == 11) {
128
+ return 30
129
+ }
130
+ return 31
131
+ }
132
+
133
+ static fn subtractOneDay:string (iso:string) {
134
+ def p (DateParts.fromIso(iso))
135
+ p.day = p.day - 1
136
+ if (p.day >= 1) {
137
+ return (p.toIso())
138
+ }
139
+ p.month = p.month - 1
140
+ if (p.month >= 1) {
141
+ p.day = (DateMath.daysInMonth(p.year p.month))
142
+ return (p.toIso())
143
+ }
144
+ p.month = 12
145
+ p.year = p.year - 1
146
+ p.day = (DateMath.daysInMonth(p.year p.month))
147
+ return (p.toIso())
148
+ }
149
+
150
+ static fn addDays:string (iso:string days:int) {
151
+ if (days == 0) {
152
+ def same (DateParts.fromIso(iso))
153
+ return (same.toIso())
154
+ }
155
+ if (days > 0) {
156
+ def p (DateParts.fromIso(iso))
157
+ def remaining@(mutable):int days
158
+ while (remaining > 0) {
159
+ p.day = p.day + 1
160
+ def dim (DateMath.daysInMonth(p.year p.month))
161
+ if (p.day > dim) {
162
+ p.day = 1
163
+ p.month = p.month + 1
164
+ if (p.month > 12) {
165
+ p.month = 1
166
+ p.year = p.year + 1
167
+ }
168
+ }
169
+ remaining = remaining - 1
170
+ }
171
+ return (p.toIso())
172
+ }
173
+ def remainingNeg@(mutable):int (0 - days)
174
+ def current:string iso
175
+ while (remainingNeg > 0) {
176
+ current = (DateMath.subtractOneDay(current))
177
+ remainingNeg = remainingNeg - 1
178
+ }
179
+ return current
180
+ }
181
+
182
+ static fn compareIso:int (a:string b:string) {
183
+ if (a == b) {
184
+ return 0
185
+ }
186
+ def pa (DateParts.fromIso(a))
187
+ def pb (DateParts.fromIso(b))
188
+ if (pa.year != pb.year) {
189
+ if (pa.year < pb.year) {
190
+ return -1
191
+ }
192
+ return 1
193
+ }
194
+ if (pa.month != pb.month) {
195
+ if (pa.month < pb.month) {
196
+ return -1
197
+ }
198
+ return 1
199
+ }
200
+ if (pa.day < pb.day) {
201
+ return -1
202
+ }
203
+ return 1
204
+ }
205
+
206
+ static fn isBetween:boolean (d:string start:string end:string) {
207
+ if ((DateMath.compareIso(d start)) < 0) {
208
+ return false
209
+ }
210
+ if ((DateMath.compareIso(d end)) > 0) {
211
+ return false
212
+ }
213
+ return true
214
+ }
215
+
216
+ static fn msPerDay:int () {
217
+ return 86400000
218
+ }
219
+ }
@@ -0,0 +1,137 @@
1
+
2
+ Import "DateMath.rgr"
3
+ Import "IsoDateParse.rgr"
4
+
5
+ ; ISO week numbers, day-of-week, and calendar offsets (no timezone).
6
+ class IsoCalendar {
7
+
8
+ sfn intDiv:int (numerator:int denominator:int) {
9
+ if (denominator <= 0) {
10
+ return 0
11
+ }
12
+ def num:double (int2double numerator)
13
+ def den:double (int2double denominator)
14
+ return (floor (num / den))
15
+ }
16
+
17
+ sfn sakamotoMonthShift:int (month:int) {
18
+ if (month == 1) {
19
+ return 0
20
+ }
21
+ if (month == 2) {
22
+ return 3
23
+ }
24
+ if (month == 3) {
25
+ return 2
26
+ }
27
+ if (month == 4) {
28
+ return 5
29
+ }
30
+ if (month == 5) {
31
+ return 0
32
+ }
33
+ if (month == 6) {
34
+ return 3
35
+ }
36
+ if (month == 7) {
37
+ return 5
38
+ }
39
+ if (month == 8) {
40
+ return 1
41
+ }
42
+ if (month == 9) {
43
+ return 4
44
+ }
45
+ if (month == 10) {
46
+ return 6
47
+ }
48
+ if (month == 11) {
49
+ return 2
50
+ }
51
+ return 4
52
+ }
53
+
54
+ sfn sakamotoDow:int (year:int month:int day:int) {
55
+ def y:int year
56
+ if (month < 3) {
57
+ y = year - 1
58
+ }
59
+ def shift:int (IsoCalendar.sakamotoMonthShift(month))
60
+ def y4:int (IsoCalendar.intDiv(y 4))
61
+ def y100:int (IsoCalendar.intDiv(y 100))
62
+ def y400:int (IsoCalendar.intDiv(y 400))
63
+ def q:int (y + y4 - y100 + y400 + shift + day)
64
+ def r:int (q % 7)
65
+ if (r < 0) {
66
+ r = r + 7
67
+ }
68
+ return r
69
+ }
70
+
71
+ sfn dayOfWeekIso:int (year:int month:int day:int) {
72
+ def dow:int (IsoCalendar.sakamotoDow(year month day))
73
+ if (dow == 0) {
74
+ return 7
75
+ }
76
+ return dow
77
+ }
78
+
79
+ sfn addDaysOffset:string (iso:string days:int) {
80
+ return (DateMath.addDays(iso days))
81
+ }
82
+
83
+ sfn daysBetween:int (fromIso:string toIso:string) {
84
+ def count:int 0
85
+ def current:string fromIso
86
+ while ((DateMath.compareIso(current toIso)) < 0) {
87
+ current = (DateMath.addDays(current 1))
88
+ count = count + 1
89
+ if (count > 4000) {
90
+ return 0
91
+ }
92
+ }
93
+ if (current == toIso) {
94
+ return count
95
+ }
96
+ return 0
97
+ }
98
+
99
+ sfn pad2:string (n:int) {
100
+ if (n < 10) {
101
+ return ("0" + (DateMath.intToString(n)))
102
+ }
103
+ return (DateMath.intToString(n))
104
+ }
105
+
106
+ sfn isoWeekKey:string (iso:string) {
107
+ def dateOnly:string (IsoDateParse.toIsoDateOnly(iso))
108
+ if ((strlen dateOnly) == 0) {
109
+ return ""
110
+ }
111
+ def week:int (IsoCalendar.getIsoWeekNumber(dateOnly))
112
+ if (week <= 0) {
113
+ return ""
114
+ }
115
+ def p:DateParts (DateParts.fromIso(dateOnly))
116
+ def isoDow:int (IsoCalendar.dayOfWeekIso(p.year p.month p.day))
117
+ def toThursday:int (4 - isoDow)
118
+ def thursdayIso:string (IsoCalendar.addDaysOffset(dateOnly toThursday))
119
+ def tp:DateParts (DateParts.fromIso(thursdayIso))
120
+ def isoYear:string (DateMath.intToString(tp.year))
121
+ return (isoYear + "-W" + (IsoCalendar.pad2(week)))
122
+ }
123
+
124
+ sfn getIsoWeekNumber:int (iso:string) {
125
+ def p:DateParts (DateParts.fromIso(iso))
126
+ def isoDow:int (IsoCalendar.dayOfWeekIso(p.year p.month p.day))
127
+ def toThursday:int (4 - isoDow)
128
+ def thursdayIso:string (IsoCalendar.addDaysOffset(iso toThursday))
129
+ def tp:DateParts (DateParts.fromIso(thursdayIso))
130
+ def jan4:string ((DateMath.intToString(tp.year)) + "-01-04")
131
+ def jan4Dow:int (IsoCalendar.dayOfWeekIso(tp.year 1 4))
132
+ def week1Monday:string (IsoCalendar.addDaysOffset(jan4 (1 - jan4Dow)))
133
+ def days:int (IsoCalendar.daysBetween(week1Monday thursdayIso))
134
+ def weeks:int (IsoCalendar.intDiv(days 7))
135
+ return (weeks + 1)
136
+ }
137
+ }
@@ -0,0 +1,57 @@
1
+
2
+ Import "DateMath.rgr"
3
+
4
+ ; Parse and validate ISO date strings without host locale APIs.
5
+ class IsoDateParse {
6
+
7
+ sfn isDigit:boolean (ch:int) {
8
+ return (ch >= 48 && ch <= 57)
9
+ }
10
+
11
+ sfn toIsoDateOnly:string (value:string) {
12
+ if ((strlen value) < 10) {
13
+ return ""
14
+ }
15
+ def candidate:string (substring value 0 10)
16
+ if ((charAt candidate 4) != 45) {
17
+ return ""
18
+ }
19
+ if ((charAt candidate 7) != 45) {
20
+ return ""
21
+ }
22
+ def i:int 0
23
+ while (i < 10) {
24
+ if (i == 4 || i == 7) {
25
+ i = i + 1
26
+ continue
27
+ }
28
+ if ((IsoDateParse.isDigit((charAt candidate i))) == false) {
29
+ return ""
30
+ }
31
+ i = i + 1
32
+ }
33
+ return candidate
34
+ }
35
+
36
+ sfn datePartsToISO:string (year:int month:int day:int) {
37
+ if (year <= 0 || month <= 0 || day <= 0) {
38
+ return ""
39
+ }
40
+ return (DateMath.formatIso(year month day))
41
+ }
42
+
43
+ sfn isoDateToYear:int (iso:string) {
44
+ def p:DateParts (DateParts.fromIso(iso))
45
+ return p.year
46
+ }
47
+
48
+ sfn isoDateToMonth:int (iso:string) {
49
+ def p:DateParts (DateParts.fromIso(iso))
50
+ return p.month
51
+ }
52
+
53
+ sfn isoDateToDay:int (iso:string) {
54
+ def p:DateParts (DateParts.fromIso(iso))
55
+ return p.day
56
+ }
57
+ }
@@ -0,0 +1,15 @@
1
+
2
+ ; Portable ISO-8601 calendar library (date-only, no timezone).
3
+ ; Convention: IsoDate = string "YYYY-MM-DD"
4
+ ;
5
+ ; Modules:
6
+ ; IsoDate/DateMath.rgr — addDays, compareIso, isBetween
7
+ ; IsoDate/IsoDateParse.rgr — toIsoDateOnly, datePartsToISO
8
+ ; IsoDate/IsoCalendar.rgr — ISO week, day-of-week
9
+ ;
10
+ ; Compiler intrinsics (Lang.rgr): iso_add_days, iso_compare, iso_between
11
+ ; Prefer DateMath.addDays in library code; intrinsics optimize hot paths per target.
12
+
13
+ Import "IsoDate/DateMath.rgr"
14
+ Import "IsoDate/IsoDateParse.rgr"
15
+ Import "IsoDate/IsoCalendar.rgr"
@@ -27,6 +27,7 @@ class RangerProcessBase {
27
27
  if (uiHost.isUiNotifySuppressed()) {
28
28
  return
29
29
  }
30
+ uiHost.notifyPathDeliveredCount = uiHost.notifyPathDeliveredCount + 1
30
31
  if ((strlen __rangerPath) > 0) {
31
32
  uiHost.notifyPath(__rangerPath)
32
33
  }
@@ -55,6 +56,21 @@ class RangerProcessBase {
55
56
  __rangerChildren = empty
56
57
  }
57
58
 
59
+ ; Outermost live ancestor (self if no parent). Used as dispatch-turn root.
60
+ fn __rangerFindRoot:RangerProcessBase () {
61
+ def cur:RangerProcessBase this
62
+ def parent@(optional):RangerProcessBase (cur.__rangerParent)
63
+ while ((null? parent) == false) {
64
+ cur = (unwrap parent)
65
+ parent = cur.__rangerParent
66
+ }
67
+ return cur
68
+ }
69
+
70
+ ; Override on app root to merge child pending* into parent before UI notify.
71
+ fn __rangerSyncChildren:void () {
72
+ }
73
+
58
74
  fn __rangerInvokeStart:void () {
59
75
  }
60
76
 
@@ -140,11 +156,17 @@ class ProcessNameRegistry @singleton(true) {
140
156
  ; Hosts may replace notifyPath/notifyId on the singleton instance; markStateDirty respects suppressUiNotify.
141
157
  class ProcessUiHost @singleton(true) {
142
158
  def __uiNotifySuppressDepth:int 0
159
+ ; Test/diagnostic: host notifyPath/notifyId deliveries (not suppressed).
160
+ def notifyPathDeliveredCount:int 0
143
161
 
144
162
  fn isUiNotifySuppressed:boolean () {
145
163
  return __uiNotifySuppressDepth > 0
146
164
  }
147
165
 
166
+ fn resetNotifyDeliveryCount:void () {
167
+ notifyPathDeliveredCount = 0
168
+ }
169
+
148
170
  fn beginSuppressUiNotify:void () {
149
171
  __uiNotifySuppressDepth = __uiNotifySuppressDepth + 1
150
172
  }
@@ -163,6 +185,31 @@ class ProcessUiHost @singleton(true) {
163
185
  }
164
186
 
165
187
  class ProcessRuntime @singleton(true) {
188
+ def __dispatchTurnDepth:int 0
189
+
190
+ sfn beginDispatchTurn:void (root:RangerProcessBase) {
191
+ def rt:ProcessRuntime (ProcessRuntime.__singleton())
192
+ if (rt.__dispatchTurnDepth == 0) {
193
+ def uiHost:ProcessUiHost (ProcessUiHost.__singleton())
194
+ uiHost.beginSuppressUiNotify()
195
+ }
196
+ rt.__dispatchTurnDepth = rt.__dispatchTurnDepth + 1
197
+ }
198
+
199
+ sfn endDispatchTurn:void (root:RangerProcessBase) {
200
+ def rt:ProcessRuntime (ProcessRuntime.__singleton())
201
+ if (rt.__dispatchTurnDepth == 0) {
202
+ return
203
+ }
204
+ rt.__dispatchTurnDepth = rt.__dispatchTurnDepth - 1
205
+ if (rt.__dispatchTurnDepth > 0) {
206
+ return
207
+ }
208
+ root.__rangerSyncChildren()
209
+ def uiHost:ProcessUiHost (ProcessUiHost.__singleton())
210
+ uiHost.endSuppressUiNotify()
211
+ root.flushUiNotify()
212
+ }
166
213
 
167
214
  sfn stopInstance:void (proc:RangerProcessBase) {
168
215
  ProcessNameRegistry.__singleton().unbindIfConfigured(proc)
@@ -0,0 +1,134 @@
1
+
2
+ ; Portable regex search: pattern and haystack are strings (no /literal/ syntax).
3
+ ; Semantics match ES6 RegExp(pattern, 'i').test(haystack) — substring search, case-insensitive.
4
+ ; Ranger-target implementation supports literals, ., *, ^, $, and \\ escapes.
5
+ ; Production targets should use the regex_test compiler intrinsic (native engines).
6
+
7
+ class RegexMatch {
8
+
9
+ static fn testIgnoreCase:boolean (pattern:string haystack:string) {
10
+ def p (to_lowercase pattern)
11
+ def h (to_lowercase haystack)
12
+ return (RegexMatch.searchPattern(p h))
13
+ }
14
+
15
+ static fn searchPattern:boolean (pat:string text:string) {
16
+ def pLen (strlen pat)
17
+ def tLen (strlen text)
18
+ if (pLen == 0) {
19
+ return true
20
+ }
21
+ def startAnchored:boolean false
22
+ def endAnchored:boolean false
23
+ def coreStart:int 0
24
+ def coreEnd:int pLen
25
+ if ((charAt pat 0) == (ccode "^")) {
26
+ startAnchored = true
27
+ coreStart = 1
28
+ }
29
+ if (pLen > 0) {
30
+ if ((charAt pat (pLen - 1)) == (ccode "$")) {
31
+ endAnchored = true
32
+ coreEnd = pLen - 1
33
+ }
34
+ }
35
+ def corePat:string (substring pat coreStart (coreEnd - coreStart))
36
+ def cLen (strlen corePat)
37
+ if (cLen == 0) {
38
+ return true
39
+ }
40
+ if (startAnchored) {
41
+ return (RegexMatch.matchAt(corePat 0 text 0 endAnchored))
42
+ }
43
+ def t@(mutable):int 0
44
+ while (t <= tLen) {
45
+ if (RegexMatch.matchAt(corePat 0 text t endAnchored)) {
46
+ return true
47
+ }
48
+ t = t + 1
49
+ }
50
+ return false
51
+ }
52
+
53
+ static fn matchAt:boolean (pat:string p:int text:string t:int requireEnd:boolean) {
54
+ def pLen (strlen pat)
55
+ def tLen (strlen text)
56
+ if (p >= pLen) {
57
+ if (requireEnd) {
58
+ return t == tLen
59
+ }
60
+ return true
61
+ }
62
+ def ch (charAt pat p)
63
+ if (ch == (ccode "\\")) {
64
+ if ((p + 1) >= pLen) {
65
+ return false
66
+ }
67
+ def esc (charAt pat (p + 1))
68
+ def nextP:int (p + 2)
69
+ if (nextP < pLen) {
70
+ def starCh (charAt pat nextP)
71
+ if (starCh == (ccode "*")) {
72
+ return (RegexMatch.matchStar(esc false nextP pat text t requireEnd))
73
+ }
74
+ }
75
+ if (t >= tLen) {
76
+ return false
77
+ }
78
+ if ((charAt text t) != esc) {
79
+ return false
80
+ }
81
+ return (RegexMatch.matchAt(pat nextP text (t + 1) requireEnd))
82
+ }
83
+ if (ch == (ccode ".")) {
84
+ def dotNext:int (p + 1)
85
+ if (dotNext < pLen) {
86
+ def starCh2 (charAt pat dotNext)
87
+ if (starCh2 == (ccode "*")) {
88
+ return (RegexMatch.matchStar((ccode ".") true dotNext pat text t requireEnd))
89
+ }
90
+ }
91
+ if (t >= tLen) {
92
+ return false
93
+ }
94
+ return (RegexMatch.matchAt(pat (p + 1) text (t + 1) requireEnd))
95
+ }
96
+ def litNext:int (p + 1)
97
+ if (litNext < pLen) {
98
+ def starCh3 (charAt pat litNext)
99
+ if (starCh3 == (ccode "*")) {
100
+ return (RegexMatch.matchStar(ch false litNext pat text t requireEnd))
101
+ }
102
+ }
103
+ if (t >= tLen) {
104
+ return false
105
+ }
106
+ if ((charAt text t) != ch) {
107
+ return false
108
+ }
109
+ return (RegexMatch.matchAt(pat (p + 1) text (t + 1) requireEnd))
110
+ }
111
+
112
+ static fn matchStar:boolean (atomChar:int isDot:boolean starPos:int pat:string text:string t:int requireEnd:boolean) {
113
+ def tLen (strlen text)
114
+ def afterStar:int (starPos + 1)
115
+ def cur@(mutable):int t
116
+ while (cur <= tLen) {
117
+ if (RegexMatch.matchAt(pat afterStar text cur requireEnd)) {
118
+ return true
119
+ }
120
+ if (cur >= tLen) {
121
+ break
122
+ }
123
+ if (isDot) {
124
+ cur = cur + 1
125
+ } {
126
+ if ((charAt text cur) != atomChar) {
127
+ break
128
+ }
129
+ cur = cur + 1
130
+ }
131
+ }
132
+ return false
133
+ }
134
+ }
@@ -0,0 +1,10 @@
1
+
2
+ ; String-based regex search (pattern as string, not /literal/ syntax).
3
+ ;
4
+ ; Modules:
5
+ ; Regex/RegexMatch.rgr — testIgnoreCase (portable subset on ranger target)
6
+ ;
7
+ ; Compiler intrinsic (Lang.rgr): regex_test(pattern, haystack)
8
+ ; Prefer regex_test in hot paths; RegexMatch.testIgnoreCase for library code on ranger target.
9
+
10
+ Import "Regex/RegexMatch.rgr"
package/dist/lib/Time.rgr CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
- ; Date functions
2
+ ; Legacy ES6-only host Date wrapper.
3
+ ; For portable calendar logic use lib/IsoDateLib.rgr (ISO strings, no timezone).
3
4
  ; https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.29.0/date_fns.min.js
4
5
 
5
6
  systemclass SystemDate {