ranger-compiler 3.0.4 → 3.1.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.1.0] - 2026-06-02
11
+
12
+ ### Added
13
+
14
+ - **`ProcessUiHost` notify suppress** — `beginSuppressUiNotify` / `endSuppressUiNotify` / `isUiNotifySuppressed` for batching parent↔child sync without re-entrant UI notify loops ([PROCESS_UI_NOTIFY.md](PROCESS_UI_NOTIFY.md))
15
+ - **Process view DTO regression fixture** — [tests/fixtures/process_view_dto_assign.rgr](tests/fixtures/process_view_dto_assign.rgr) (cross-class field assignment with method call on RHS)
16
+ - **Docs** — [PROCESS_UI_NOTIFY.md](PROCESS_UI_NOTIFY.md), [PROCESS_UI_VIEW_MODELS.md](PROCESS_UI_VIEW_MODELS.md); README `@process` quick start
17
+
18
+ ### Fixed
19
+
20
+ - **Parser: assignment RHS method calls** — `row.field = this.helper(index)` no longer splits the call into invalid `=` operands ([PROCESS_UI_VIEW_MODELS.md](PROCESS_UI_VIEW_MODELS.md)); fix in [compiler/ng_RangerFlowParser.rgr](compiler/ng_RangerFlowParser.rgr) (`repairAssignMethodCallRhs`)
21
+
22
+ ### Changed
23
+
24
+ - Version bumped from `3.0.5` to `3.1.0`
25
+
26
+ ## [3.0.5] - 2026-05-29
27
+
28
+ ### Added
29
+
30
+ - **`@process` messaging** — `proc_send` operator; `receiveMessage` on `RangerProcessBase`; `find_process` / `ProcessNameRegistry.findProcess`
31
+ - **TypeScript named paths** — `-typescript` emits `ProcessPath`, `findProcess` on registry, singleton `new` returns shared instance
32
+ - **`RangerProcess.rgr` in npm lib** — included under `dist/lib/` when running `npm run build:dist`
33
+
34
+ ### Fixed
35
+
36
+ - **`build:dist:copy`** — `rm -rf dist/lib` before copy so new lib files (e.g. `RangerProcess.rgr`) land in `dist/lib/` instead of nested `dist/lib/lib/`
37
+
38
+ ### Changed
39
+
40
+ - Version bumped from `3.0.4` to `3.0.5`
41
+
10
42
  ## [3.0.4] - 2026-05-12
11
43
 
12
44
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ranger cross language compiler
2
2
 
3
- **Version 3.0.4** | Status: `experimental`
3
+ **Version 3.1.0** | Status: `experimental`
4
4
 
5
5
  Ranger is a self-hosting cross-language compiler for writing portable algorithms, parsers, generators, and small tools once and compiling them to multiple target languages.
6
6
 
@@ -25,8 +25,42 @@ Ranger is best approached today as a compiler and language lab with practical mu
25
25
 
26
26
  If you want one sentence of positioning: Ranger is currently more convincing as a portable algorithm compiler / DSL toolchain than as a drop-in replacement for mainstream application languages.
27
27
 
28
+ ## `@process` runtime (experimental)
29
+
30
+ Ranger can mark classes with `@process` to get a **small object runtime** in generated code (especially JavaScript/TypeScript): parent/child tree, instance registry, `proc_start` / `proc_stop`, and UI refresh via `markStateDirty()`.
31
+
32
+ | Piece | What it does |
33
+ | --- | --- |
34
+ | `@process` / `@process(true)` | Process instance with lifecycle hooks (`start`, `stop`, …) |
35
+ | `proc_start` / `proc_stop` | Activate or tear down a process subtree (children first) |
36
+ | `proc_send target handler arg…` | Typed message dispatch to `fn on…` handlers on a live instance |
37
+ | `ProcessNameRegistry.findProcess(path)` | Lookup by `@name("app.foo")` (TypeScript path literals when using `-typescript`) |
38
+ | `markStateDirty()` | Bump generation + notify host (`ProcessUiHost`) for UI binding |
39
+ | `beginSuppressUiNotify` / `endSuppressUiNotify` | Batch parent↔child sync without notify storms |
40
+
41
+ **Typical app shape:** domain logic and UI flags live in `@process` classes; **view DTOs** are plain Ranger classes filled by a builder; React/CLI/native hosts subscribe to `ProcessUiHost` and call `findProcess` — I/O and async work stay in the host (Node, Swift, …), not inside the process bytecode.
42
+
43
+ ```ranger
44
+ class CounterPage @process @name("app.counter") extends RangerProcessBase {
45
+ def count:int 0
46
+ fn onUiIncrement:void () {
47
+ count = count + 1
48
+ this.markStateDirty()
49
+ }
50
+ }
51
+ ```
52
+
53
+ ```typescript
54
+ // Host (TypeScript): wire notify once, then bind UI to process fields
55
+ const host = ProcessUiHost.__singleton();
56
+ host.notifyPath = (path) => { /* sync view model + re-render */ };
57
+ ```
58
+
59
+ **Docs:** [PROCESS_MVP.md](PROCESS_MVP.md) (scope), [PROCESS_STATUS.md](PROCESS_STATUS.md) (compiler checklist), [PROCESS_UI_NOTIFY.md](PROCESS_UI_NOTIFY.md) (notify batching), [PROCESS_UI_VIEW_MODELS.md](PROCESS_UI_VIEW_MODELS.md) (view DTO assignment). **Gallery:** [process_counter_board](gallery/process_counter_board/README.md). **Pilot:** [realtrainer `app-ranger` Active Workout demo](https://github.com/terotests/realtrainer/tree/copilot/create-watch-ui-components/app-ranger/demo/active-workout-process).
60
+
28
61
  ## Where To Start
29
62
 
63
+ - [Online playground](https://terotests.github.io/Ranger/) — try Ranger in the browser (`playground/`, Vite + current compiler)
30
64
  - `README.md` - language overview, installation, and syntax notes
31
65
  - `ai/QUICKREF.md` - fast reference for syntax and core concepts
32
66
  - `ai/INSTRUCTIONS.md` - fuller language guide for operators, templates, and compiler concepts
package/dist/Lang.rgr CHANGED
@@ -193,9 +193,11 @@ language {
193
193
  templates {
194
194
  ranger ("(cast " (e 1) " " (e 2) ":" (typeof 2) " )")
195
195
  ts ( (e 1) " as " (typeof 2) )
196
+ kotlin ( (e 1) " as " (typeof 2) )
196
197
  es6 ( (e 1) )
197
198
  java7 ( "((" (typeof 2) ")" (e 1) ")" )
198
199
  swift3 ( (e 1) " as " (typeof 2) "" )
200
+ swift6 ( (e 1) " as " (typeof 2) "" )
199
201
  cpp ( "mpark::get<" (typeof 2) ">(" (e 1) ")"
200
202
 
201
203
  (plugin 'makefile' ((dep 'variant.hpp' 'https://github.com/mpark/variant/releases/download/v1.2.2/variant.hpp')))
@@ -493,6 +495,7 @@ func r_io_get_env( name string) *GoNullable {
493
495
  swift6 ("CommandLine.arguments[" (e 1) " + 1]")
494
496
  es6 ( "process.argv[ 2 + " (e 1) "]")
495
497
  rust ( "std::env::args().nth((" (e 1) " + 1) as usize).unwrap_or_default()")
498
+ kotlin ( "args[" (e 1) "]")
496
499
  ranger ("( shell_arg " (e 1) " )")
497
500
  }
498
501
  }
@@ -508,6 +511,7 @@ func r_io_get_env( name string) *GoNullable {
508
511
  go ( "int64( len( os.Args) - 1 )" (imp "os"))
509
512
  es6 ( "(process.argv.length - 2)" )
510
513
  rust ( "(std::env::args().len() as i64 - 1)")
514
+ kotlin ( "(args.size - 1)")
511
515
  ranger ("( shell_arg_cnt )")
512
516
  }
513
517
  }
@@ -930,6 +934,7 @@ fn r_read_file(path: &str, filename: &str) -> Option<String> {
930
934
  }
931
935
  "))
932
936
  es6 ( "(require('fs').readFileSync( " (e 1) " + '/' + " (e 2) " , 'utf8'))" )
937
+ kotlin ( "try { java.io.File(if (" (e 1) ".isEmpty()) " (e 2) " else " (e 1) " + \"/\" + " (e 2) ").readText() } catch (_: Exception) { null }" (imp "java.io.File") )
933
938
  }
934
939
  }
935
940
 
@@ -2716,6 +2721,7 @@ std::vector<uint8_t> r_buffer_read_file(const std::string& path, const std::stri
2716
2721
  ranger ( "(to_int " (e 1) ")")
2717
2722
  csharp ( "(int)" (e 1 ) "" )
2718
2723
  swift3 ( "Int(" (e 1 ) ")" )
2724
+ swift6 ( "Int(" (e 1 ) ")" )
2719
2725
  kotlin ( (e 1 ) ".toInt()" )
2720
2726
  scala ( (e 1 ) ".toInt" )
2721
2727
  rust ( (e 1 ) " as i64 " )
@@ -3165,9 +3171,50 @@ func r_str_2_i64(s string) *GoNullable {
3165
3171
  )
3166
3172
  php ( "is_numeric(" (e 1) ") ? intval(" (e 1) ") : NULL ")
3167
3173
  scala ( "Try(" (e 1) ".toInt).toOption" (imp "scala.util.Try"))
3168
- kotlin ( (e 1) ".toIntOrNull()")
3169
- swift3 ("Int(" (e 1) ")")
3170
- swift6 ("Int(" (e 1) ")")
3174
+ kotlin ( "rangerStr2IntPrefix(" (e 1) ")"
3175
+ (create_polyfill "
3176
+ fun rangerStr2IntPrefix(s: String): Int? {
3177
+ val m = Regex(\"^-?\\\\d+\").find(s) ?: return null
3178
+ return m.value.toIntOrNull()
3179
+ }
3180
+ ")
3181
+ )
3182
+ swift3 ("rangerStr2IntPrefix(" (e 1) ")"
3183
+ (create_polyfill "
3184
+ func rangerStr2IntPrefix(_ s: String) -> Int? {
3185
+ var i = s.startIndex
3186
+ if i < s.endIndex && s[i] == \"-\" {
3187
+ i = s.index(after: i)
3188
+ }
3189
+ var end = i
3190
+ while end < s.endIndex && s[end].isNumber {
3191
+ end = s.index(after: end)
3192
+ }
3193
+ if end == i {
3194
+ return nil
3195
+ }
3196
+ return Int(s[i..<end])
3197
+ }
3198
+ ")
3199
+ )
3200
+ swift6 ("rangerStr2IntPrefix(" (e 1) ")"
3201
+ (create_polyfill "
3202
+ func rangerStr2IntPrefix(_ s: String) -> Int? {
3203
+ var i = s.startIndex
3204
+ if i < s.endIndex && s[i] == \"-\" {
3205
+ i = s.index(after: i)
3206
+ }
3207
+ var end = i
3208
+ while end < s.endIndex && s[end].isNumber {
3209
+ end = s.index(after: end)
3210
+ }
3211
+ if end == i {
3212
+ return nil
3213
+ }
3214
+ return Int(s[i..<end])
3215
+ }
3216
+ ")
3217
+ )
3171
3218
  csharp( "Int32.Parse(" (e 1) ")" )
3172
3219
  rust ( (e 1) ".parse::<i64>().ok()" )
3173
3220
  * ( "isNaN( parseInt(" (e 1) ") ) ? undefined : parseInt(" (e 1) ")")
@@ -3247,7 +3294,7 @@ Double _getDoubleOrNull ( String str ) {
3247
3294
  scala ( "Try(" (e 1) ".toDouble).toOption" (imp "scala.util.Try"))
3248
3295
  kotlin ( (e 1) ".toDoubleOrNull()")
3249
3296
  swift3 ("Double(" (e 1) ")")
3250
- rust ( (e 1) ".parse::<f64>().ok()" )
3297
+ swift6 ("Double(" (e 1) ")")
3251
3298
  * ( "isNaN( parseFloat(" (e 1) ") ) ? undefined : parseFloat(" (e 1) ")")
3252
3299
  }
3253
3300
  }
@@ -4090,9 +4137,12 @@ public <T> T _arr_extract( ArrayList<T> list, Integer i ) {
4090
4137
  "const readline = require('readline');" nl
4091
4138
  "readline.emitKeypressEvents(process.stdin);" nl
4092
4139
  "if (process.stdin.isTTY) { process.stdin.setRawMode(true); }" nl
4093
- "process.stdin.on('keypress', (" (e 1) ", data) => {" nl I
4094
- "if (data && data.ctrl && data.name === 'c') { process.stdout.write('\\x1b[?25h'); process.exit(); }" nl
4095
- "if (data && data.name) { " (e 1) " = data.name; global.r_key_queue.push(" (e 1) "); }" nl
4140
+ "process.stdin.resume();" nl
4141
+ "process.stdin.on('keypress', (str, key) => {" nl I
4142
+ "if (key && key.ctrl && key.name === 'c') { process.stdout.write('\\x1b[?25h'); process.exit(); }" nl
4143
+ "var __rgr_k = \"\";" nl
4144
+ "if (key && key.name) { __rgr_k = key.name; } else if (str && str.length > 0) { __rgr_k = str; } else if (key && key.sequence) { __rgr_k = key.sequence; }" nl
4145
+ "if (__rgr_k !== \"\") { " (e 1) " = __rgr_k; global.r_key_queue.push(__rgr_k); }" nl
4096
4146
  (block 2)
4097
4147
  i nl "});" nl
4098
4148
  )
@@ -4519,7 +4569,11 @@ var r_key_queue: [String] = []
4519
4569
  var r_key_queue_lock = NSLock()
4520
4570
 
4521
4571
  func r_setup_raw_mode() {
4522
- let _ = system(\"stty cbreak min 1 -echo\")
4572
+ let p = Process()
4573
+ p.executableURL = URL(fileURLWithPath: \"/bin/stty\")
4574
+ p.arguments = [\"-F\", \"/dev/tty\", \"cbreak\", \"min\", \"1\", \"-echo\"]
4575
+ try? p.run()
4576
+ p.waitUntilExit()
4523
4577
  }
4524
4578
 
4525
4579
  func r_read_key() -> String? {
@@ -4609,7 +4663,11 @@ func r_read_key() -> String? {
4609
4663
  }
4610
4664
  #else
4611
4665
  func r_setup_raw_mode() {
4612
- let _ = system(\"stty cbreak min 1 -echo\")
4666
+ let p = Process()
4667
+ p.executableURL = URL(fileURLWithPath: \"/bin/stty\")
4668
+ p.arguments = [\"-F\", \"/dev/tty\", \"cbreak\", \"min\", \"1\", \"-echo\"]
4669
+ try? p.run()
4670
+ p.waitUntilExit()
4613
4671
  }
4614
4672
 
4615
4673
  func r_read_key() -> String? {
@@ -4863,8 +4921,10 @@ std::string r_cpp_str_to_uppercase(std::string original)
4863
4921
  go ("float64( " (e 1) " )")
4864
4922
  es6 ( (e 1) )
4865
4923
  swift3 ("Double(" (e 1) ")")
4924
+ swift6 ("Double(" (e 1) ")")
4866
4925
  scala ( (e 1) '.toDouble')
4867
4926
  java7 ("Double.valueOf(" (e 1) ")")
4927
+ kotlin ( (e 1) ".toDouble()" )
4868
4928
  cpp ("(double)(" (e 1) ")")
4869
4929
  php ( (e 1) )
4870
4930
  rust ("(" (e 1) " as f64)")
@@ -5280,6 +5340,79 @@ std::string r_cpp_str_to_uppercase(std::string original)
5280
5340
  }
5281
5341
  }
5282
5342
 
5343
+ ; Process lifecycle operators (distinct from HttpServer start/stop)
5344
+ find_process@(moves@( 1 )) cmdFindProcess:RangerProcessBase@(optional) (path:string) {
5345
+ templates {
5346
+ es6 ( "ProcessNameRegistry.__singleton().findByPath(" (e 1) ")" )
5347
+ kotlin ( "ProcessNameRegistry.__singleton().findByPath(" (e 1) ")" )
5348
+ swift6 ( "ProcessNameRegistry.__singleton().findByPath(" (e 1) ")" )
5349
+ swift3 ( "ProcessNameRegistry.__singleton().findByPath(" (e 1) ")" )
5350
+ * ( "/* find_process not implemented */" )
5351
+ }
5352
+ }
5353
+
5354
+ ; proc_send is lowered in ng_RangerProcessProcSend.rgr (typed handler methods, overload match).
5355
+
5356
+ proc_start@(moves@( 1 )) cmdProcStart:void (proc:RangerProcessBase) {
5357
+ templates {
5358
+ es6 ( "ProcessRuntime.startInstance(" (e 1) ")" )
5359
+ kotlin ( "ProcessRuntime.startInstance(" (e 1) ")" )
5360
+ swift6 ( "ProcessRuntime.startInstance(proc: " (e 1) ")" )
5361
+ swift3 ( "ProcessRuntime.startInstance(proc: " (e 1) ")" )
5362
+ * ( "/* proc_start not implemented */" )
5363
+ }
5364
+ }
5365
+
5366
+ proc_stop@(moves@( 1 )) cmdProcStopInst:void (proc:RangerProcessBase) {
5367
+ templates {
5368
+ es6 ( "ProcessRuntime.stopInstance(" (e 1) ")" )
5369
+ kotlin ( "ProcessRuntime.stopInstance(" (e 1) ")" )
5370
+ swift6 ( "ProcessRuntime.stopInstance(proc: " (e 1) ")" )
5371
+ swift3 ( "ProcessRuntime.stopInstance(proc: " (e 1) ")" )
5372
+ * ( "/* proc_stop instance not implemented */" )
5373
+ }
5374
+ }
5375
+
5376
+ proc_stop@(moves@( 1 )) cmdProcStopProcessId:void (processId:int) {
5377
+ templates {
5378
+ es6 ( "ProcessRuntime.stopByProcessId(" (e 1) ")" )
5379
+ kotlin ( "ProcessRuntime.stopByProcessId(" (e 1) ")" )
5380
+ swift6 ( "ProcessRuntime.stopByProcessId(" (e 1) ")" )
5381
+ swift3 ( "ProcessRuntime.stopByProcessId(" (e 1) ")" )
5382
+ * ( "/* proc_stop processId not implemented */" )
5383
+ }
5384
+ }
5385
+
5386
+ proc_stop@(moves@( 1 )) cmdProcStopClass:void (className:string) {
5387
+ templates {
5388
+ es6 ( "ProcessRuntime.stopByClassName(" (e 1) ")" )
5389
+ kotlin ( "ProcessRuntime.stopByClassName(" (e 1) ")" )
5390
+ swift6 ( "ProcessRuntime.stopByClassName(" (e 1) ")" )
5391
+ swift3 ( "ProcessRuntime.stopByClassName(" (e 1) ")" )
5392
+ * ( "/* proc_stop class not implemented */" )
5393
+ }
5394
+ }
5395
+
5396
+ proc_hibernate@(moves@( 1 )) cmdProcHibernate:string (proc:RangerProcessBase) {
5397
+ templates {
5398
+ es6 ( "ProcessRuntime.hibernateInstance(" (e 1) ")" )
5399
+ kotlin ( "ProcessRuntime.hibernateInstance(" (e 1) ")" )
5400
+ swift6 ( "ProcessRuntime.hibernateInstance(proc: " (e 1) ")" )
5401
+ swift3 ( "ProcessRuntime.hibernateInstance(proc: " (e 1) ")" )
5402
+ * ( "\"\"" )
5403
+ }
5404
+ }
5405
+
5406
+ proc_wakeup@(moves@( 2 )) cmdProcWakeup:void (proc:RangerProcessBase state:string) {
5407
+ templates {
5408
+ es6 ( "ProcessRuntime.wakeupInstance(" (e 1) ", " (e 2) ")" )
5409
+ kotlin ( "ProcessRuntime.wakeupInstance(" (e 1) ", " (e 2) ")" )
5410
+ swift6 ( "ProcessRuntime.wakeupInstance(proc: " (e 1) ", state: " (e 2) ")" )
5411
+ swift3 ( "ProcessRuntime.wakeupInstance(proc: " (e 1) ", state: " (e 2) ")" )
5412
+ * ( "/* proc_wakeup not implemented */" )
5413
+ }
5414
+ }
5415
+
5283
5416
  }
5284
5417
 
5285
5418
 
package/dist/lib/JSON.rgr CHANGED
@@ -90,10 +90,37 @@ operators {
90
90
  java7 ( (e 1) ".isNull( " (e 2) " ) ? null : " (e 1) ".optString(" (e 2) ") " )
91
91
  kotlin ( "(if (" (e 1) ".isNull(" (e 2) ")) null else " (e 1) ".optString(" (e 2) "))"
92
92
  (create_polyfill "
93
- typealias JSONObject = org.json.JSONObject
94
- typealias JSONArray = org.json.JSONArray
95
- typealias JSONDataObject = org.json.JSONObject
96
- typealias JSONArrayObject = org.json.JSONArray
93
+ open class JSONObject {
94
+ protected open val values: MutableMap<String, Any?> = mutableMapOf()
95
+ open fun isNull(key: String): Boolean = !values.containsKey(key) || values[key] == null
96
+ open fun optString(key: String): String? = values[key]?.toString()
97
+ open fun optInt(key: String): Int? = when (val v = values[key]) {
98
+ is Int -> v
99
+ is Double -> v.toInt()
100
+ is String -> v.toIntOrNull()
101
+ else -> null
102
+ }
103
+ open fun optDouble(key: String): Double? = when (val v = values[key]) {
104
+ is Double -> v
105
+ is Int -> v.toDouble()
106
+ is String -> v.toDoubleOrNull()
107
+ else -> null
108
+ }
109
+ open fun optBoolean(key: String): Boolean? = values[key] as? Boolean
110
+ open fun getJSONObject(key: String): JSONObject? = values[key] as? JSONObject
111
+ open fun getJSONArray(key: String): JSONArray? = values[key] as? JSONArray
112
+ open fun put(key: String, value: Any?): JSONObject { values[key] = value; return this }
113
+ override fun toString(): String = values.toString()
114
+ constructor(source: String) : this() {}
115
+ constructor()
116
+ }
117
+ open class JSONArray : ArrayList<Any?>() {
118
+ open fun length(): Int = size
119
+ override fun get(index: Int): Any? = if (index in indices) super.get(index) else null
120
+ override fun toString(): String = joinToString(prefix = \"[\", postfix = \"]\")
121
+ }
122
+ typealias JSONDataObject = JSONObject
123
+ typealias JSONArrayObject = JSONArray
97
124
  ")
98
125
  )
99
126
  go ( "r_get_opt_json_string(" (e 1) ", " (e 2) ")"
@@ -0,0 +1,304 @@
1
+
2
+ Import "stdops.rgr"
3
+
4
+ class RangerProcessBase {
5
+ def __rangerId:int 0
6
+ def __rangerParentId:int 0
7
+ def __rangerTypeId:int 0
8
+ def __rangerClassName:string ""
9
+ def __rangerPath:string ""
10
+ def __rangerStateGeneration:int 0
11
+ def __rangerParent:RangerProcessBase@(optional)
12
+ def __rangerChildren:[RangerProcessBase]
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
+
19
+ fn markStateDirty:void () {
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
+ if ((strlen __rangerPath) > 0) {
31
+ uiHost.notifyPath(__rangerPath)
32
+ }
33
+ if (__rangerId != 0) {
34
+ uiHost.notifyId(__rangerId)
35
+ }
36
+ }
37
+
38
+ fn __rangerOnDescendantUiChanged:void (child:RangerProcessBase hint:string) {
39
+ this.markStateDirty()
40
+ def parent@(optional):RangerProcessBase (__rangerParent)
41
+ if ((null? parent) == false) {
42
+ def p:RangerProcessBase (unwrap parent)
43
+ if (p.__rangerId != 0) {
44
+ p.__rangerOnDescendantUiChanged(child hint)
45
+ }
46
+ }
47
+ }
48
+
49
+ fn __rangerTrackChild:void (child:RangerProcessBase) {
50
+ push __rangerChildren child
51
+ }
52
+
53
+ fn __rangerClearChildren:void () {
54
+ def empty:[RangerProcessBase]
55
+ __rangerChildren = empty
56
+ }
57
+
58
+ fn __rangerInvokeStart:void () {
59
+ }
60
+
61
+ fn __rangerInvokeStop:void () {
62
+ }
63
+
64
+ fn __rangerInvokeHibernate:string () {
65
+ return ""
66
+ }
67
+
68
+ fn __rangerInvokeWakeup:void (state:string) {
69
+ }
70
+
71
+ fn __rangerStopSubtree:void () {
72
+ }
73
+
74
+ ; Legacy hook; prefer typed proc_send → handler methods on @process classes.
75
+ fn receiveMessage:void (name:string value:string) {
76
+ }
77
+ }
78
+
79
+ ; Single app-wide ID space for all @process instances (roots and children).
80
+ class ProcessIdRegistry @singleton(true) {
81
+ def next:int 1
82
+
83
+ fn allocate:int () {
84
+ def id (this.next)
85
+ this.next = id + 1
86
+ return id
87
+ }
88
+ }
89
+
90
+ class ProcessNameRegistry @singleton(true) {
91
+ def byPath:[string:RangerProcessBase]
92
+
93
+ fn register:void (path:string proc:RangerProcessBase) {
94
+ if ((strlen path) == 0) {
95
+ return
96
+ }
97
+ if (proc.__rangerId == 0) {
98
+ return
99
+ }
100
+ def existing@(optional):RangerProcessBase (get byPath path)
101
+ if ((null? existing) == false) {
102
+ def old:RangerProcessBase (unwrap existing)
103
+ if (old.__rangerId != 0 && old.__rangerId != proc.__rangerId) {
104
+ print ("ERROR: duplicate live @name process path: " + path)
105
+ return
106
+ }
107
+ }
108
+ set byPath path proc
109
+ }
110
+
111
+ fn unregister:void (path:string) {
112
+ ; Map entry may remain until overwritten; hasLive/findByPath check __rangerId.
113
+ }
114
+
115
+ fn findByPath@(optional):RangerProcessBase (path:string) {
116
+ return (get byPath path)
117
+ }
118
+
119
+ fn hasLive:boolean (path:string) {
120
+ def found@(optional):RangerProcessBase (get byPath path)
121
+ if (null? found) {
122
+ return false
123
+ }
124
+ def inst:RangerProcessBase (unwrap found)
125
+ if (inst.__rangerId == 0) {
126
+ return false
127
+ }
128
+ return true
129
+ }
130
+
131
+ fn unbindIfConfigured:void (proc:RangerProcessBase) {
132
+ if ((strlen proc.__rangerPath) > 0) {
133
+ this.unregister(proc.__rangerPath)
134
+ proc.__rangerPath = ""
135
+ }
136
+ }
137
+ }
138
+
139
+ ; Host-side UI subscriptions (React useProcess wires listeners in TS).
140
+ ; Hosts may replace notifyPath/notifyId on the singleton instance; markStateDirty respects suppressUiNotify.
141
+ class ProcessUiHost @singleton(true) {
142
+ def __uiNotifySuppressDepth:int 0
143
+
144
+ fn isUiNotifySuppressed:boolean () {
145
+ return __uiNotifySuppressDepth > 0
146
+ }
147
+
148
+ fn beginSuppressUiNotify:void () {
149
+ __uiNotifySuppressDepth = __uiNotifySuppressDepth + 1
150
+ }
151
+
152
+ fn endSuppressUiNotify:void () {
153
+ if (__uiNotifySuppressDepth > 0) {
154
+ __uiNotifySuppressDepth = __uiNotifySuppressDepth - 1
155
+ }
156
+ }
157
+
158
+ fn notifyPath:void (path:string) {
159
+ }
160
+
161
+ fn notifyId:void (processId:int) {
162
+ }
163
+ }
164
+
165
+ class ProcessRuntime @singleton(true) {
166
+
167
+ sfn stopInstance:void (proc:RangerProcessBase) {
168
+ ProcessNameRegistry.__singleton().unbindIfConfigured(proc)
169
+ proc.__rangerStopSubtree()
170
+ }
171
+
172
+ sfn startInstance:void (proc:RangerProcessBase) {
173
+ ProcessNameRegistry.__singleton().bindIfConfigured(proc)
174
+ proc.__rangerInvokeStart()
175
+ }
176
+
177
+ sfn hibernateInstance:string (proc:RangerProcessBase) {
178
+ def res:string (proc.__rangerInvokeHibernate())
179
+ return res
180
+ }
181
+
182
+ sfn wakeupInstance:void (proc:RangerProcessBase state:string) {
183
+ proc.__rangerInvokeWakeup(state)
184
+ }
185
+ }
186
+
187
+ class RangerFieldDescriptor {
188
+ def name:string ""
189
+ def typeName:string ""
190
+ def isOptional:boolean false
191
+ }
192
+
193
+ class RangerClassDescriptor {
194
+ def className:string ""
195
+ def fields:[RangerFieldDescriptor]
196
+ }
197
+
198
+ ; CLI tree renderer for @process hierarchies (__rangerChildren). Root list:
199
+ ; ProcessRuntime.collectAllLiveRoots() (generated when the app has @process classes).
200
+ class ProcessTreeView {
201
+ def showParentId:boolean true
202
+
203
+ fn isLive:boolean (proc:RangerProcessBase) {
204
+ if (proc.__rangerId == 0) {
205
+ return false
206
+ }
207
+ return true
208
+ }
209
+
210
+ fn nodeLabel:string (proc:RangerProcessBase) {
211
+ def label:string ""
212
+ label = proc.__rangerClassName
213
+ label = label + " #"
214
+ label = label + (to_string proc.__rangerId)
215
+ if (showParentId) {
216
+ label = label + " parent="
217
+ label = label + (to_string proc.__rangerParentId)
218
+ }
219
+ return label
220
+ }
221
+
222
+ fn countLiveChildren:int (proc:RangerProcessBase) {
223
+ def n:int 0
224
+ for proc.__rangerChildren ch:RangerProcessBase i {
225
+ if (this.isLive(ch)) {
226
+ n = n + 1
227
+ }
228
+ }
229
+ return n
230
+ }
231
+
232
+ fn renderSubtree:void (proc:RangerProcessBase indent:string) {
233
+ def liveIdx:int 0
234
+ def total:int (this.countLiveChildren(proc))
235
+ for proc.__rangerChildren ch:RangerProcessBase i {
236
+ if ((this.isLive(ch)) == false) {
237
+ continue
238
+ }
239
+ def branch:string "|-> "
240
+ def childIndent:string " "
241
+ if (liveIdx == (total - 1)) {
242
+ branch = "L-> "
243
+ }
244
+ def line:string ""
245
+ line = indent + branch
246
+ def nodeText:string (this.nodeLabel(ch))
247
+ line = line + nodeText
248
+ print line
249
+ def nextIndent:string ""
250
+ nextIndent = indent + childIndent
251
+ this.renderSubtree(ch nextIndent)
252
+ liveIdx = liveIdx + 1
253
+ }
254
+ }
255
+
256
+ fn countLiveRoots:int (roots:[RangerProcessBase]) {
257
+ def c:int 0
258
+ for roots r:RangerProcessBase i {
259
+ if (this.isLive(r)) {
260
+ c = c + 1
261
+ }
262
+ }
263
+ return c
264
+ }
265
+
266
+ fn renderRoots:void (roots:[RangerProcessBase] title:string) {
267
+ print ""
268
+ if (title != "") {
269
+ print title
270
+ } {
271
+ print "Process tree"
272
+ }
273
+ def n:int (size roots)
274
+ if (n == 0) {
275
+ print " (no live root processes)"
276
+ return
277
+ }
278
+ def liveTotal:int (this.countLiveRoots(roots))
279
+ if (liveTotal == 0) {
280
+ print " (no live root processes)"
281
+ return
282
+ }
283
+ def shown:int 0
284
+ def i:int 0
285
+ while (i < n) {
286
+ def root:RangerProcessBase (at roots i)
287
+ if (this.isLive(root)) {
288
+ def branch:string "|-> "
289
+ def childIndent:string " "
290
+ if (shown == (liveTotal - 1)) {
291
+ branch = "L-> "
292
+ }
293
+ def line:string ""
294
+ line = " " + branch
295
+ def rootText:string (this.nodeLabel(root))
296
+ line = line + rootText
297
+ print line
298
+ this.renderSubtree(root childIndent)
299
+ shown = shown + 1
300
+ }
301
+ i = i + 1
302
+ }
303
+ }
304
+ }