ranger-compiler 3.1.1 → 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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.2.0] - 2026-08-01
11
+
12
+ ### Fixed
13
+
14
+ - **`@serialize(true)` array / hash / property types without the annotation** — the generator assumed every referenced class had `toDictionary` / `fromDictionary` and emitted the call regardless, so the compile failed inside `extension <Class>` with `Could not match argument types for push` / `for case` and never named the real cause. Hash-valued fields were worse: the field was silently dropped from the serialized output. A `@serialize(true)` class that references a class which is neither `@serialize(true)` nor implements the pair by hand now fails at the declaration:
15
+
16
+ ```text
17
+ [FAIL] Parent.kids: [Child] can not be serialized - class Child is not @serialize(true).
18
+ Add @serialize(true) to Child, or implement toDictionary / fromDictionary in it.
19
+ 9 │ def kids:[Child]
20
+ ^── here
21
+ ```
22
+
23
+ - **`@serialize(true)` with mutually referencing classes** — serializer extensions were generated one class at a time and `is_serialized` was set as each was reached, so whichever class came first in the dependency sort saw its peer as non-serializable and emitted primitive element code. All `@serialize(true)` classes are now marked before any extension is generated, which mutual references could not satisfy by sorting.
24
+
25
+ - **`@serialize(true)` element types with hand-written `toDictionary` / `fromDictionary`** — accepted as serializable; the generator now routes them through the object path instead of treating them as primitives.
26
+
27
+ - **Duplicate serializer extension when one file is reached by two import spellings** — a file imported as both `Child.rgr` and `domain/Child.rgr` was expanded twice, and with `@serialize(true)` the second expansion produced `method with the same name and parameter signature declared earlier` pointing at generated code. Fixed since 3.1.1 was published, released here; covered by a regression test.
28
+
29
+ - **`SourceCode` constructed with three arguments in `ng_CodeNode.rgr`** — `SourceCode` takes a single `code_str`, but `vref1`, `vref2`, `newStr`, `newBool` and nine sibling factories passed `(name 0 (strlen name))` or `("" 0 0)`, copying the shape of the adjacent `new CodeNode(code 0 ...)`. JavaScript discards the extra arguments, so this was invisible until the TypeScript API bundle was type checked, where it produced all 13 of the `TS2554: Expected 1 arguments, but got 3` errors that `build:dist:module` reported. Dropping the dead arguments makes `tsc` clean, so the step is `&&`-chained again and a real type error blocks the build instead of scrolling past
30
+
31
+ - **`file_mtime` C++ backend** — added missing `cpp` template in `Lang.rgr` (`stat()` + ms, matching es6 `mtimeMs`); fixes `game_runtime.rgr` hot-reload compile for `game_sdl` native binary
32
+
33
+ - **`TSLexer` UTF-8 / native C++ tokenization** — code-unit length vs byte `charAt` mismatch after multi-byte characters (e.g. em dash in comments) no longer desyncs the lexer; fixes native SDL parse failures on [`invaders.game.tsx`](./gallery/game_engine/scripting/invaders.game.tsx) and similar scripts
34
+
35
+ ### Added
36
+
37
+ - **LPC spritesheet compositor (standalone MVP)** — Ranger-native `gallery/game_engine/lpc/` (`png_decoder.rgr`, layer blit, male walk demo); embedded `pack/demo-male-walk` (~28 KB); `npm run engine:lpc:build` / `engine:lpc:run`; design doc [`LPC_HEADLESS_SPRITESHEET.md`](./gallery/game_engine/LPC_HEADLESS_SPRITESHEET.md)
38
+
39
+ - **JPEG background loading for scripted games** — `GameImageLoader` + `GameRunner` asset queue (`resources()`, `backgroundImage()`, `onLoading()`); `splash_demo.game.tsx` smoke test; `game.d.ts` `ResourceDef` / `LoadingProps` types
40
+
41
+ - **In-process TS hot reload (Path A)** — `TSAstPatcher` (`ts_ast_patch.rgr`), `ComponentEngine.patchScript()`, `GameRunner` runtime options (`setHotReload`, `trackScriptFile`, `maybeHotReload`); `game_sdl --hot-reload` / `--no-hot-reload`; default on for interactive SDL, off for `maxFrames` smoke runs
42
+
43
+ - **SDL + native compiled games (Path B)** — `game_sdl_native_host.rgr`, `invaders_native_sdl_runner.rgr`, `build-game-sdl-native.sh`; `npm run engine:game-sdl-native:run:invaders` runs emitter output in an SDL window without `ComponentEngine`
44
+
45
+ - **`@serialize(true)` regression suite** — `tests/compiler-serialize.test.ts` and `tests/fixtures/serialize/`: round trip over primitives, nested objects, object arrays and object hashes; mutual references; hand-written `toDictionary` / `fromDictionary`; duplicate import spellings; and the three diagnostics above
46
+
47
+ ### Changed
48
+
49
+ - **`prepublishOnly` no longer runs the whole repository suite** — publishing the compiler ran all 56 test files, including the gallery, game-engine and native-toolchain suites. Those need SDL2, `g++`, Cannon and game fixtures that ship with neither the repo nor the package, so `npm publish` failed on the publisher's machine for reasons unrelated to the compiler (missing `SDL2/SDL.h`, `gallery/game_engine/games/ylos/index.tsx` and `physics_race/index.tsx` are absent from the repository entirely). `prepublishOnly` and `.github/workflows/publish.yml` now run `npm run test:publish` — 44 files, 355 tests, ~90s — covering parsing, type checking and code generation for every target backend. `npm test` still runs everything, and `ci.yml` is unchanged.
50
+
51
+ - **`build:dist` now includes `build:dist:module`** — `dist/api.js` was not rebuilt by the release build, which is how it drifted behind `bin/output.js`
52
+
53
+ - **`dist/api.js` rebuilt from current sources** — the committed programmatic-API bundle predated several compiler fixes, so `require("ranger-compiler")` shipped older behaviour than the `rgrc` CLI. `scripts/patch-chain-desugar.js` now patches `dist/api.js` as well as `bin/output.js`, and `build:dist:module` runs it after `tsc`
54
+
10
55
  ## [3.1.1] - 2026-06-23
11
56
 
12
57
  ### Changed
package/README.md CHANGED
@@ -69,6 +69,7 @@ host.notifyPath = (path) => { /* sync view model + re-render */ };
69
69
  - `gallery/js_parser` - substantial parser example with benchmarks and README
70
70
  - `gallery/pdf_writer` - EVG / TSX document tooling and preview server
71
71
  - `gallery/invaders` - cross-target demo game
72
+ - `gallery/game_engine` - retained-mode game runner, SDL launcher, and TSX games (Pong, Breakout, Invaders, Pac-Man); see `gallery/game_engine/scripting/GAME_SCRIPTING.md`
72
73
  - `gallery/invaders/llvm/invaders.ll` - checked-in LLVM IR sample from the experimental `-l=llvm` backend
73
74
 
74
75
  ## Compatibility Snapshot
@@ -84,6 +85,23 @@ The project can target `JavaScript`, `Java`, `Go`, `Swift`, `PHP`, `C++`, `C#`,
84
85
  | **LLVM / WASM** (`-l=llvm`) | **Experimental.** Lowers to LLVM IR; optional WAT export for freestanding WASM. Native libc builds work for demos like Space Invaders; browser WASM is still rough. See `npm run test:llvm`, `npm run game:build:llvm`. |
85
86
  | Gallery examples | Good for understanding direction and capability, but some require manual setup or platform-specific tooling |
86
87
 
88
+ ### Conformance suite (Track 1)
89
+
90
+ Cross-target semantic fixtures live in `tests/conformance/`. Run `npx vitest run tests/compiler-conformance.test.ts`.
91
+ Regenerate the fixture list with `node scripts/generate-conformance-table.mjs`.
92
+
93
+ <!-- BEGIN CONFORMANCE_TABLE -->
94
+ | Fixture | Topic | Targets |
95
+ | --- | --- | --- |
96
+ | `array_param_mutate` | array parameters use reference semantics (Issue #58; Go known gap) | ES6, Go, Kotlin (when toolchain present) |
97
+ | `clear_then_push` | clear resets slice without nil, push refills (Issue #59) | ES6, Go, Kotlin (when toolchain present) |
98
+ | `int_division_to_double` | Conformance: integer division promoted to double (Issue #4) | ES6, Go, Kotlin (when toolchain present) |
99
+ | `lf_line_endings` | LF-only source (Issue #12 class must not break operator spacing) | ES6, Go, Kotlin (when toolchain present) |
100
+ | `math_ops` | Conformance: arithmetic and comparisons | ES6, Go, Kotlin (when toolchain present) |
101
+ | `string_codepoint_index` | Conformance: Unicode code-point string indexing (Issue #57) | ES6, Go, Kotlin (when toolchain present) |
102
+ | `while_loop` | Conformance: while loop control flow | ES6, Go, Kotlin (when toolchain present) |
103
+ <!-- END CONFORMANCE_TABLE -->
104
+
87
105
  ## What's New in Version 3.0
88
106
 
89
107
  - **New File Extension** - Transitioning from `.clj` to `.rgr` for Ranger identity
@@ -434,7 +452,7 @@ npm run demo:wasm # smaller freestanding WASM demo (tests/fixtures/ll
434
452
 
435
453
  **Checked-in sample:** [`gallery/invaders/llvm/invaders.ll`](gallery/invaders/llvm/invaders.ll) is LLVM IR kept in the repo so you can inspect codegen without building. Regenerate with `cp tmp/invaders-native/invaders.ll gallery/invaders/llvm/invaders.ll` after `game:build:llvm` (see [`gallery/invaders/llvm/README.md`](gallery/invaders/llvm/README.md)).
436
454
 
437
- Status: experimental — libc-linked native builds are the most reliable path; freestanding WAT/WASM for the full game is still incomplete (terminal imports, control-flow lowering). Smaller WASM demos under `npm run demo:wasm` are closer to working in the browser.
455
+ Status: experimental — libc-linked native builds are the most reliable path. The freestanding WAT/WASM path (`-wasmrc`) now has a reference-counting runtime (free-list heap with `memory.grow`, typedesc-driven recursive object destruction), classes with reference-counted fields, singletons, strings, `[T]`/`[K:V]` collections, and lambdas/closures (function table + `call_indirect`, with value/object capture and mutation); the `gallery/game_engine/games/ranger_autopeli` guest is authored in Ranger and compiles to WASM this way. Terminal-import lowering for the full Space Invaders game is still incomplete. Smaller freestanding demos run under `npm run demo:wasm`.
438
456
 
439
457
  #### Cross-Compiling the Game
440
458
 
@@ -1329,6 +1347,8 @@ this.foo({
1329
1347
  })
1330
1348
  ```
1331
1349
 
1350
+ Lambdas compile on every backend, including the freestanding WASM/WAT path (`-wasmrc`): each body is hoisted to a function-table entry and calls go through `call_indirect`, with the value a reference-counted closure record. Captured variables are copied (values/strings) or retained (objects); a captured object can be mutated through the closure, and a captured value can be shared by boxing it in a heap cell.
1351
+
1332
1352
  # Automatically infixed math support
1333
1353
 
1334
1354
  It is easy to define new mathematical operations in the Lang.clj file or in modules. However, some mathematical operations are automatically infixed
package/dist/Lang.rgr CHANGED
@@ -60,7 +60,7 @@ language {
60
60
  llvm LLVM IR ll
61
61
  }
62
62
 
63
- toplevel_keywords (class systemclass Constructor fn sfn Import Extend Enum def let operators)
63
+ toplevel_keywords (class record systemclass Constructor fn sfn Import Extend Enum def let operators)
64
64
  annotation_keywors (weak strong lives temp)
65
65
 
66
66
  ; transformations for the reserved words for function names or other keywords
@@ -80,12 +80,34 @@ language {
80
80
  class _class
81
81
  new _new
82
82
  delete _delete
83
+ and _and
84
+ or _or
85
+ not _not
86
+ xor _xor
87
+ compl _compl
88
+ bitand _bitand
89
+ bitor _bitor
90
+ and_eq _and_eq
91
+ or_eq _or_eq
92
+ xor_eq _xor_eq
93
+ not_eq _not_eq
83
94
  template _template
84
95
  namespace _namespace
85
96
  virtual _virtual
86
97
  public _public
87
98
  private _private
88
99
  protected _protected
100
+ signed _signed
101
+ unsigned _unsigned
102
+ register _register
103
+ volatile _volatile
104
+ mutable _mutable
105
+ friend _friend
106
+ typename _typename
107
+ typedef _typedef
108
+ extern _extern
109
+ explicit _explicit
110
+ goto _goto
89
111
  }
90
112
  go {
91
113
  type _type
@@ -222,6 +244,19 @@ language {
222
244
  templates {
223
245
  es6 ('Math.random()')
224
246
  kotlin ('Math.random()' (imp "java.lang.Math"))
247
+ cpp ( "rg_random_double()" (imp "<random>")
248
+ (create_polyfill "
249
+ double rg_random_double() {
250
+ static thread_local std::mt19937 _rg{std::random_device{}()};
251
+ static thread_local std::uniform_real_distribution<double> _rd(0.0, 1.0);
252
+ return _rd(_rg);
253
+ }
254
+ ")
255
+ )
256
+ go ( 'rand.Float64()' (imp "math/rand") )
257
+ ; std-only (rustc, no crates): each RandomState::new() draws a fresh
258
+ ; OS-seeded key, so hashing a constant yields a varying 53-bit value.
259
+ rust ( '{ use std::hash::{BuildHasher, Hasher}; let mut h = std::collections::hash_map::RandomState::new().build_hasher(); h.write_u64(0); ((h.finish() >> 11) as f64) / ((1u64 << 53) as f64) }' )
225
260
  }
226
261
  }
227
262
 
@@ -413,11 +448,27 @@ func _r_md5(value string) string {
413
448
  tan tan:double ( v:double ) {
414
449
  templates {
415
450
  es6 ("Math.tan(" (e 1) ")")
416
- go ( "math.Tan(" (e 1) ")" (imp "math"))
417
- swift3 ( "tan(" (e 1) ")" (imp "Foundation"))
418
- java7 ( "Math.tan(" (e 1) ")" (imp "java.lang.Math"))
419
- php ( "tan(" (e 1) ")")
420
- cpp ("tan(" (e 1) ")" (imp "<math.h>"))
451
+ go ( "math.Tan(" (e 1) ")" (imp "math"))
452
+ swift3 ( "tan(" (e 1) ")" (imp "Foundation"))
453
+ java7 ( "Math.tan(" (e 1) ")" (imp "java.lang.Math"))
454
+ php ( "tan(" (e 1) ")")
455
+ cpp ("tan(" (e 1) ")" (imp "<math.h>"))
456
+ }
457
+ }
458
+
459
+ atan2 atan2:double ( y:double x:double ) {
460
+ templates {
461
+ ranger ( "(atan2 " (e 1) " " (e 2) ")" )
462
+ es6 ( "Math.atan2(" (e 1) ", " (e 2) ")" )
463
+ go ( "math.Atan2(" (e 1) ", " (e 2) ")" (imp "math"))
464
+ swift3 ( "atan2(" (e 1) ", " (e 2) ")" (imp "Foundation"))
465
+ java7 ( "Math.atan2(" (e 1) ", " (e 2) ")" (imp "java.lang.Math"))
466
+ php ( "atan2(" (e 1) ", " (e 2) ")" )
467
+ csharp ( "Math.Atan2(" (e 1) ", " (e 2) ")" (imp "System"))
468
+ rust ( "" (e 1) ".atan2(" (e 2) ")" )
469
+ scala ( "math.atan2(" (e 1) ", " (e 2) ")" (imp "scala.math"))
470
+ cpp ( "atan2(" (e 1) ", " (e 2) ")" (imp "<math.h>"))
471
+ * ( "Math.atan2(" (e 1) ", " (e 2) ")" )
421
472
  }
422
473
  }
423
474
 
@@ -713,7 +764,7 @@ func r_dir_exists(pathName string) bool {
713
764
  }
714
765
  }
715
766
 
716
- ; Returns the file modification time as Unix timestamp (seconds since epoch)
767
+ ; Returns the file modification time (ms on es6/cpp; seconds on go)
717
768
  file_mtime cmdFileMTime:int (path:string filename:string) {
718
769
  templates {
719
770
  go ( "r_file_mtime(" (e 1) ", " (e 2) ")"
@@ -730,6 +781,18 @@ func r_file_mtime(pathName string, fileName string) int64 {
730
781
  )
731
782
  )
732
783
  es6 ( "require('fs').statSync(" (e 1) " + '/' + " (e 2) ").mtimeMs" )
784
+ cpp ( "r_file_mtime(" (e 1) ", " (e 2) ")" (imp "<sys/stat.h>") (imp "<string>")
785
+ (create_polyfill "
786
+ int64_t r_file_mtime(const std::string& path, const std::string& filename) {
787
+ struct stat st;
788
+ std::string full = path + \"/\" + filename;
789
+ if (stat(full.c_str(), &st) != 0) {
790
+ return 0;
791
+ }
792
+ return (int64_t)st.st_mtime * 1000;
793
+ }
794
+ ")
795
+ )
733
796
  ranger ( "(file_mtime " (e 1) " " (e 2) ")" )
734
797
  }
735
798
  }
@@ -1310,6 +1373,24 @@ fn r_read_file(path: &str, filename: &str) -> Option<String> {
1310
1373
  }
1311
1374
  }
1312
1375
 
1376
+ ; Integer division (truncates toward zero). Use instead of / when both
1377
+ ; operands are int — plain / returns double for int/int in Ranger.
1378
+ idiv _:int (left:int right:int) {
1379
+ templates {
1380
+ ranger @macro(true) ( "(/ " (e 1) " " (e 2) ")" )
1381
+ cpp ( "((" (e 1) ") / (" (e 2) "))" )
1382
+ llvm ( "(idiv " (e 1) " " (e 2) ")" )
1383
+ es6 ( "((" (e 1) " / " (e 2) ") | 0)" )
1384
+ go ( (e 1) " / " (e 2) )
1385
+ rust ( "((" (e 1) ") / (" (e 2) "))" )
1386
+ kotlin ( (e 1) " / " (e 2) )
1387
+ python ( "((" (e 1) ") // (" (e 2) "))" )
1388
+ java7 ( "((" (e 1) ") / (" (e 2) "))" )
1389
+ php ( "intdiv(" (e 1) ", " (e 2) ")" )
1390
+ * ( (e 1) " / " (e 2) )
1391
+ }
1392
+ }
1393
+
1313
1394
  ; ============================================================================
1314
1395
  ; Bitwise operators for integer manipulation
1315
1396
  ; ============================================================================
@@ -1449,12 +1530,16 @@ fn r_read_file(path: &str, filename: &str) -> Option<String> {
1449
1530
  * cmdMulOp:double ( left:double right:double ) { templates { * ( (e 1) " * " (e 2) ) } }
1450
1531
  * cmdMulOp:int ( left:int right:int ) { templates { * ( (e 1) " * " (e 2) ) } }
1451
1532
 
1452
- / cmdDivOp:double ( left:double right:double ) { templates { * ( (e 1) " / " (e 2) ) } }
1453
- / cmdDivOp:double ( left:int right:int ) { templates {
1533
+ / cmdDivOp:double ( left:double right:double ) { templates {
1534
+ cpp ( "((double)(" (e 1) ") / (double)(" (e 2) "))" )
1535
+ * ( (e 1) " / " (e 2) )
1536
+ } }
1537
+ / cmdDivOp:double ( left:int right:int ) { templates {
1454
1538
  go ( "float64(" (e 1) ") / float64(" (e 2) ")" )
1455
1539
  rust ( "(" (e 1) " as f64) / (" (e 2) " as f64)" )
1456
1540
  kotlin ( (e 1) ".toDouble() / " (e 2) ".toDouble()" )
1457
- * ( (e 1) " / " (e 2) )
1541
+ cpp ( "((double)(" (e 1) ") / (double)(" (e 2) "))" )
1542
+ * ( (e 1) " / " (e 2) )
1458
1543
  } }
1459
1544
 
1460
1545
  ; optional string operator...
@@ -1687,7 +1772,22 @@ fn r_read_file(path: &str, filename: &str) -> Option<String> {
1687
1772
  templates {
1688
1773
  cpp ( "if ( " (e 1) ".has_value ) {" nl I (block 2) i nl "}" nl )
1689
1774
  }
1690
- }
1775
+ }
1776
+
1777
+ ; typed optional int/double with an else branch: r_optional_primitive is
1778
+ ; a value type (has_value field), not a pointer, so the generic :T
1779
+ ; `!= NULL` form below does not compile under clang. Emit `.has_value`.
1780
+ if cmdIf:void ( condition@(optional):int then_block:block else_block ) {
1781
+ templates {
1782
+ cpp ( "if ( " (e 1) ".has_value ) {" I nl (block 2) i nl "} else {" nl I (block 3) i "}" nl )
1783
+ }
1784
+ }
1785
+
1786
+ if cmdIf:void ( condition@(optional):double then_block:block else_block ) {
1787
+ templates {
1788
+ cpp ( "if ( " (e 1) ".has_value ) {" I nl (block 2) i nl "} else {" nl I (block 3) i "}" nl )
1789
+ }
1790
+ }
1691
1791
 
1692
1792
  if cmdIf:void ( condition@(optional):T then_block:block ) {
1693
1793
  templates {
@@ -1930,9 +2030,18 @@ std::vector<T> r_make_vector_from_array( const T (&data)[N] )
1930
2030
  }
1931
2031
  null? cmdIsNotNull:boolean ( arg@(optional):string ) {
1932
2032
  templates {
1933
- cpp ((e 1) ".empty()")
2033
+ cpp ((e 1) ".empty()")
1934
2034
  }
1935
- }
2035
+ }
2036
+
2037
+ ; buffer is a value type (std::vector<uint8_t>) in C++, so the generic
2038
+ ; `!= NULL` path below does not compile. Treat empty/absent as null,
2039
+ ; mirroring the string overload above.
2040
+ null? cmdIsNotNull:boolean ( arg@(optional):buffer ) {
2041
+ templates {
2042
+ cpp ((e 1) ".empty()")
2043
+ }
2044
+ }
1936
2045
 
1937
2046
  null? cmdIsNull:boolean ( arg@(optional):T ) {
1938
2047
  templates {
@@ -1964,9 +2073,18 @@ std::vector<T> r_make_vector_from_array( const T (&data)[N] )
1964
2073
  }
1965
2074
  !null? cmdIsNotNull:boolean ( arg@(optional):string ) {
1966
2075
  templates {
1967
- cpp ((e 1) ".empty() == false ")
2076
+ cpp ((e 1) ".empty() == false ")
1968
2077
  }
1969
- }
2078
+ }
2079
+
2080
+ ; buffer is a value type (std::vector<uint8_t>) in C++; the generic
2081
+ ; `!= NULL` path below does not compile. Present == non-empty, matching
2082
+ ; the string overload above.
2083
+ !null? cmdIsNotNull:boolean ( arg@(optional):buffer ) {
2084
+ templates {
2085
+ cpp ((e 1) ".empty() == false ")
2086
+ }
2087
+ }
1970
2088
 
1971
2089
  !null? cmdIsNotNull:boolean ( arg@(optional):T ) {
1972
2090
  templates {
@@ -2123,7 +2241,7 @@ case class customException(smth:String) extends Exception
2123
2241
  php ( "trim(" (e 1 ) ")")
2124
2242
  cpp ( "r_cpp_trim( " (e 1) ")" (imp "<cctype>") (imp "<string>") (imp "<algorithm>")
2125
2243
  (create_polyfill "
2126
- inline std::string r_cpp_trim(std::string &s)
2244
+ inline std::string r_cpp_trim(const std::string &s)
2127
2245
  {
2128
2246
  auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
2129
2247
  auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
@@ -3016,10 +3134,10 @@ std::vector<std::string> r_get_keys_of_map( std::map<std::string, T> orig_map )
3016
3134
  (create_polyfill '
3017
3135
  std::string r_utf8_substr(const std::string& str, int start_i, int leng_i)
3018
3136
  {
3019
- unsigned int start ((unsigned int)start_i);
3020
- unsigned int leng ((unsigned int)leng_i);
3137
+ size_t start = (size_t)start_i;
3138
+ size_t leng = (size_t)leng_i;
3021
3139
  if (leng==0) { return ""; }
3022
- unsigned int c, i, ix, q, min= (unsigned int) std::string::npos, max=(unsigned int)std::string::npos;
3140
+ size_t c, i, ix, q, min= std::string::npos, max= std::string::npos;
3023
3141
  for (q=0, i=0, ix=str.length(); i < ix; i++, q++)
3024
3142
  {
3025
3143
  if (q==start){ min=i; }
@@ -3538,6 +3656,49 @@ r_optional_primitive<int> cpp_get_map_int_value( std::map<" (r_ktype 1) ", int>
3538
3656
  )
3539
3657
  }
3540
3658
  }
3659
+
3660
+ get cmdGet@(optional weak):double ( map:[K:double] key:K ) {
3661
+ templates {
3662
+ cpp( "cpp_get_map_string_double_value(" (e 1) ", " (e 2) ")"
3663
+ (create_polyfill
3664
+ "
3665
+ template <class T>
3666
+ class r_optional_primitive {
3667
+ public:
3668
+ bool has_value;
3669
+ T value;
3670
+ r_optional_primitive<T> & operator=(const r_optional_primitive<T> & rhs) {
3671
+ has_value = rhs.has_value;
3672
+ value = rhs.value;
3673
+ return *this;
3674
+ }
3675
+ r_optional_primitive<T> & operator=(const T a_value) {
3676
+ has_value = true;
3677
+ value = a_value;
3678
+ return *this;
3679
+ }
3680
+ };
3681
+ "
3682
+ )
3683
+ (macro
3684
+ ("
3685
+ r_optional_primitive<double> cpp_get_map_string_double_value( std::map<std::string, double> m , std::string key) {
3686
+ r_optional_primitive<double> result;
3687
+ auto it = m.find(key);
3688
+ if (it != m.end()) {
3689
+ result.value = it->second;
3690
+ result.has_value = true;
3691
+ }
3692
+ return result;
3693
+ }")
3694
+ )
3695
+ )
3696
+ ranger ( "(get " (e 1) " " (e 2) ")")
3697
+ llvm ( "(get " (e 1) " " (e 2) ")")
3698
+ es6 ( "( " (e 1) ".hasOwnProperty(" (e 2) ") ? " (e 1) "[" (e 2) "] : undefined )" )
3699
+ * ( (e 1) "[" (e 2) "]" )
3700
+ }
3701
+ }
3541
3702
 
3542
3703
  get cmdGet@(optional weak):T ( map:[K:T] key:K ) {
3543
3704
  templates {
@@ -3545,6 +3706,21 @@ r_optional_primitive<int> cpp_get_map_int_value( std::map<" (r_ktype 1) ", int>
3545
3706
  llvm ( "(get " (e 1) " " (e 2) ")")
3546
3707
  java7 ( (e 1) ".get(" (e 2) ")" )
3547
3708
  rust ( (e 1) ".get(" (e 2) ")" )
3709
+ cpp ( "r_map_get_val(" (e 1) ", " (e 2) ")"
3710
+ (create_polyfill
3711
+ "
3712
+ // Map read that never inserts on miss (unlike std::map::operator[]) and does a
3713
+ // single lookup. Returns a default-constructed value (e.g. null shared_ptr)
3714
+ // when the key is absent.
3715
+ template<typename M, typename K>
3716
+ typename M::mapped_type r_map_get_val( const M & m , const K & key) {
3717
+ auto it = m.find(key);
3718
+ if ( it != m.end() ) { return it->second; }
3719
+ return typename M::mapped_type();
3720
+ }
3721
+ "
3722
+ )
3723
+ )
3548
3724
 
3549
3725
  scala ( (e 1) ".get(" (e 2) ")" )
3550
3726
 
@@ -5007,6 +5183,20 @@ fun r_read_key(): String {
5007
5183
  swift6 ((e 1) ".lowercased()")
5008
5184
  scala ((e 1) ".toLowerCase()")
5009
5185
  python ((e 1) ".lower()")
5186
+ cpp (
5187
+ "r_cpp_str_to_lowercase(" (e 1) ")"
5188
+ (imp "<algorithm>")
5189
+ (imp "<string>")
5190
+ (create_polyfill "
5191
+ std::string r_cpp_str_to_lowercase(std::string original)
5192
+ {
5193
+ std::string str = original;
5194
+ std::transform(str.begin(), str.end(), str.begin(), ::tolower);
5195
+ return str;
5196
+ }
5197
+ ")
5198
+
5199
+ )
5010
5200
  }
5011
5201
  }
5012
5202
  to_uppercase _:string (s:string) {