ranger-compiler 3.0.5 → 3.1.1
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.
- package/CHANGELOG.md +41 -0
- package/README.md +114 -8
- package/dist/Lang.rgr +149 -13
- package/dist/lib/IsoDate/DateMath.rgr +219 -0
- package/dist/lib/IsoDate/IsoCalendar.rgr +137 -0
- package/dist/lib/IsoDate/IsoDateParse.rgr +57 -0
- package/dist/lib/IsoDateLib.rgr +15 -0
- package/dist/lib/RangerProcess.rgr +82 -4
- package/dist/lib/Regex/RegexMatch.rgr +134 -0
- package/dist/lib/RegexLib.rgr +10 -0
- package/dist/lib/Time.rgr +2 -1
- package/dist/rgrc.js +30176 -22193
- package/package.json +13 -4
|
@@ -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"
|
|
@@ -11,13 +11,28 @@ class RangerProcessBase {
|
|
|
11
11
|
def __rangerParent:RangerProcessBase@(optional)
|
|
12
12
|
def __rangerChildren:[RangerProcessBase]
|
|
13
13
|
|
|
14
|
+
; Bump revision only (e.g. parent sync merging child pending*). Prefer over markStateDirty in sync blocks.
|
|
15
|
+
fn bumpStateGeneration:void () {
|
|
16
|
+
__rangerStateGeneration = __rangerStateGeneration + 1
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
fn markStateDirty:void () {
|
|
15
20
|
__rangerStateGeneration = __rangerStateGeneration + 1
|
|
21
|
+
this.flushUiNotify()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
; Notify host without bumping __rangerStateGeneration (after suppressUiNotify batch).
|
|
25
|
+
fn flushUiNotify:void () {
|
|
26
|
+
def uiHost:ProcessUiHost (ProcessUiHost.__singleton())
|
|
27
|
+
if (uiHost.isUiNotifySuppressed()) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
uiHost.notifyPathDeliveredCount = uiHost.notifyPathDeliveredCount + 1
|
|
16
31
|
if ((strlen __rangerPath) > 0) {
|
|
17
|
-
|
|
32
|
+
uiHost.notifyPath(__rangerPath)
|
|
18
33
|
}
|
|
19
34
|
if (__rangerId != 0) {
|
|
20
|
-
|
|
35
|
+
uiHost.notifyId(__rangerId)
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
|
|
@@ -41,6 +56,21 @@ class RangerProcessBase {
|
|
|
41
56
|
__rangerChildren = empty
|
|
42
57
|
}
|
|
43
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
|
+
|
|
44
74
|
fn __rangerInvokeStart:void () {
|
|
45
75
|
}
|
|
46
76
|
|
|
@@ -57,8 +87,8 @@ class RangerProcessBase {
|
|
|
57
87
|
fn __rangerStopSubtree:void () {
|
|
58
88
|
}
|
|
59
89
|
|
|
60
|
-
;
|
|
61
|
-
fn receiveMessage:void (
|
|
90
|
+
; Legacy hook; prefer typed proc_send → handler methods on @process classes.
|
|
91
|
+
fn receiveMessage:void (name:string value:string) {
|
|
62
92
|
}
|
|
63
93
|
}
|
|
64
94
|
|
|
@@ -123,7 +153,30 @@ class ProcessNameRegistry @singleton(true) {
|
|
|
123
153
|
}
|
|
124
154
|
|
|
125
155
|
; Host-side UI subscriptions (React useProcess wires listeners in TS).
|
|
156
|
+
; Hosts may replace notifyPath/notifyId on the singleton instance; markStateDirty respects suppressUiNotify.
|
|
126
157
|
class ProcessUiHost @singleton(true) {
|
|
158
|
+
def __uiNotifySuppressDepth:int 0
|
|
159
|
+
; Test/diagnostic: host notifyPath/notifyId deliveries (not suppressed).
|
|
160
|
+
def notifyPathDeliveredCount:int 0
|
|
161
|
+
|
|
162
|
+
fn isUiNotifySuppressed:boolean () {
|
|
163
|
+
return __uiNotifySuppressDepth > 0
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn resetNotifyDeliveryCount:void () {
|
|
167
|
+
notifyPathDeliveredCount = 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn beginSuppressUiNotify:void () {
|
|
171
|
+
__uiNotifySuppressDepth = __uiNotifySuppressDepth + 1
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn endSuppressUiNotify:void () {
|
|
175
|
+
if (__uiNotifySuppressDepth > 0) {
|
|
176
|
+
__uiNotifySuppressDepth = __uiNotifySuppressDepth - 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
127
180
|
fn notifyPath:void (path:string) {
|
|
128
181
|
}
|
|
129
182
|
|
|
@@ -132,6 +185,31 @@ class ProcessUiHost @singleton(true) {
|
|
|
132
185
|
}
|
|
133
186
|
|
|
134
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
|
+
}
|
|
135
213
|
|
|
136
214
|
sfn stopInstance:void (proc:RangerProcessBase) {
|
|
137
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