@aperturesyndicate/synx-format 3.6.0 → 3.6.3
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/README.md +12 -10
- package/SPECIFICATION.md +3 -2
- package/bin/synx.js +2 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/calc.d.ts +1 -1
- package/dist/calc.js +2 -2
- package/dist/calc.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +239 -29
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +98 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +450 -5
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +224 -49
- package/dist/parser.js.map +1 -1
- package/dist/synx.browser.js +23 -23
- package/dist/synx.browser.js.map +3 -3
- package/dist/synx.browser.mjs +23 -23
- package/dist/synx.browser.mjs.map +3 -3
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +62 -62
package/README.md
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
> Main SYNX site: https://synx.aperturesyndicate.com/
|
|
2
|
+
|
|
3
|
+
# SYNX for JS/TS � @aperturesyndicate/synx-format
|
|
2
4
|
|
|
3
5
|
The official JavaScript & TypeScript parser for the SYNX format.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install @aperturesyndicate/synx
|
|
10
|
+
npm install @aperturesyndicate/synx-format
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## Usage
|
|
12
14
|
|
|
13
15
|
```javascript
|
|
14
|
-
const Synx = require('@aperturesyndicate/synx');
|
|
16
|
+
const Synx = require('@aperturesyndicate/synx-format');
|
|
15
17
|
|
|
16
18
|
// Load from file
|
|
17
19
|
const data = Synx.loadSync('config.synx');
|
|
@@ -25,7 +27,7 @@ console.log(data2.name); // "Wario"
|
|
|
25
27
|
### TypeScript
|
|
26
28
|
|
|
27
29
|
```typescript
|
|
28
|
-
import Synx from '@aperturesyndicate/synx';
|
|
30
|
+
import Synx from '@aperturesyndicate/synx-format';
|
|
29
31
|
|
|
30
32
|
interface Config {
|
|
31
33
|
server: { port: number; host: string };
|
|
@@ -40,10 +42,10 @@ console.log(data.server.port); // typed as number
|
|
|
40
42
|
|
|
41
43
|
| Method | Description |
|
|
42
44
|
|---|---|
|
|
43
|
-
| `Synx.parse<T>(text, options?)` | Parse a .synx string
|
|
45
|
+
| `Synx.parse<T>(text, options?)` | Parse a .synx string ? object |
|
|
44
46
|
| `Synx.loadSync<T>(path, options?)` | Load & parse file (sync) |
|
|
45
47
|
| `Synx.load<T>(path, options?)` | Load & parse file (async, returns Promise) |
|
|
46
|
-
| `Synx.stringify(obj, active?)` | Serialize object
|
|
48
|
+
| `Synx.stringify(obj, active?)` | Serialize object ? .synx string |
|
|
47
49
|
|
|
48
50
|
### Options
|
|
49
51
|
|
|
@@ -63,7 +65,7 @@ console.log(data.server.port); // typed as number
|
|
|
63
65
|
Install globally:
|
|
64
66
|
|
|
65
67
|
```bash
|
|
66
|
-
npm install -g @aperturesyndicate/synx
|
|
68
|
+
npm install -g @aperturesyndicate/synx-format
|
|
67
69
|
```
|
|
68
70
|
|
|
69
71
|
### Commands
|
|
@@ -127,8 +129,8 @@ volume[min:0, max:100, type:int] 75
|
|
|
127
129
|
|
|
128
130
|
## Other Languages
|
|
129
131
|
|
|
130
|
-
- **Python**
|
|
131
|
-
- **Rust**
|
|
132
|
+
- **Python** � [synx-format](https://pypi.org/project/synx-format/) on PyPI
|
|
133
|
+
- **Rust** � [synx](https://crates.io/crates/synx) on crates.io
|
|
132
134
|
```bash
|
|
133
135
|
cargo add synx
|
|
134
136
|
```
|
|
@@ -141,4 +143,4 @@ volume[min:0, max:100, type:int] 75
|
|
|
141
143
|
|
|
142
144
|
## License
|
|
143
145
|
|
|
144
|
-
MIT
|
|
146
|
+
MIT � � APERTURESyndicate
|
package/SPECIFICATION.md
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
The normative specification files live in the monorepo (not duplicated here for npm):
|
|
4
4
|
|
|
5
|
-
- English: [`docs/spec/SPECIFICATION_EN.md`](https://github.com/
|
|
6
|
-
- Russian: [`docs/spec/SPECIFICATION_RU.md`](https://github.com/
|
|
5
|
+
- English: [`docs/spec/SPECIFICATION_EN.md`](https://github.com/APERTURESyndicate/synx-format/blob/main/docs/spec/SPECIFICATION_EN.md)
|
|
6
|
+
- Russian: [`docs/spec/SPECIFICATION_RU.md`](https://github.com/APERTURESyndicate/synx-format/blob/main/docs/spec/SPECIFICATION_RU.md)
|
|
7
|
+
- Normative (SYNX 3.6): [`docs/spec/SYNX-3.6-NORMATIVE.md`](https://github.com/APERTURESyndicate/synx-format/blob/main/docs/spec/SYNX-3.6-NORMATIVE.md)
|
package/bin/synx.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
@@ -14,7 +14,7 @@ SYNX CLI — The Active Data Format (v3.6.0)
|
|
|
14
14
|
|
|
15
15
|
DEPRECATED: This Node.js CLI is superseded by the native Rust CLI.
|
|
16
16
|
Install: cargo install synx-cli
|
|
17
|
-
Docs: https://github.com/
|
|
17
|
+
Docs: https://github.com/APERTURESyndicate/synx-format#cli-rust
|
|
18
18
|
|
|
19
19
|
Usage:
|
|
20
20
|
synx convert <file.synx> [--format json|yaml|toml|env] [--strict] [--active]
|
package/dist/browser.d.ts
CHANGED
package/dist/browser.js
CHANGED
package/dist/calc.d.ts
CHANGED
package/dist/calc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* SYNX Safe Calculator — @aperturesyndicate/synx
|
|
3
|
+
* SYNX Safe Calculator — @aperturesyndicate/synx-format
|
|
4
4
|
*
|
|
5
5
|
* Evaluates arithmetic expressions WITHOUT eval() or new Function().
|
|
6
6
|
* Supports: +, -, *, /, %, parentheses, and numeric literals.
|
|
@@ -49,7 +49,7 @@ function tokenize(expr) {
|
|
|
49
49
|
i++;
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
|
-
throw new Error(`SYNX :calc — unexpected character: '${ch}' in expression
|
|
52
|
+
throw new Error(`SYNX :calc — unexpected character: '${ch}' in expression`);
|
|
53
53
|
}
|
|
54
54
|
return tokens;
|
|
55
55
|
}
|
package/dist/calc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calc.js","sourceRoot":"","sources":["../src/calc.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAqJH,4BAIC;AAlJD;;GAEG;AACH,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEnB,kBAAkB;QAClB,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAC9B,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,qEAAqE;QACrE,IACE,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG,CAAC;YACxB,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC;YAC/E,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,EAC3K,CAAC;YACD,IAAI,GAAG,GAAG,EAAE,CAAC;YACb,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,GAAG,IAAI,GAAG,CAAC;gBACX,CAAC,EAAE,CAAC;YACN,CAAC;YACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;gBAClF,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;gBACf,CAAC,EAAE,CAAC;YACN,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxD,SAAS;QACX,CAAC;QAED,YAAY;QACZ,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACvC,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,cAAc;QACd,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,uCAAuC,EAAE,
|
|
1
|
+
{"version":3,"file":"calc.js","sourceRoot":"","sources":["../src/calc.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
CHANGED
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA0B,WAAW,EAAe,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA0B,WAAW,EAAe,MAAM,SAAS,CAAC;AAuG5F,wBAAgB,OAAO,CACrB,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,WAAgB,EACzB,IAAI,CAAC,EAAE,UAAU,EACjB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,aAAa,SAAI,EACjB,YAAY,SAAK,GAChB,UAAU,CAujBZ"}
|
package/dist/engine.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* SYNX Engine — @aperturesyndicate/synx
|
|
3
|
+
* SYNX Engine — @aperturesyndicate/synx-format
|
|
4
4
|
*
|
|
5
5
|
* Resolves active markers (:random, :calc, :env, :alias, :secret, etc.)
|
|
6
6
|
* in a parsed SYNX object tree. Only runs in !active mode.
|
|
@@ -25,19 +25,29 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
|
25
25
|
const DEFAULT_MAX_INCLUDE_DEPTH = 16;
|
|
26
26
|
/** Maximum object nesting depth for active-mode resolution (prevents stack overflow). */
|
|
27
27
|
const MAX_RESOLVE_DEPTH = 512;
|
|
28
|
-
/** Ensure a file path stays inside the base directory (path jail).
|
|
28
|
+
/** Ensure a file path stays inside the base directory (path jail).
|
|
29
|
+
* Error messages are kept in lock-step with the Rust engine's `jail_path`. */
|
|
29
30
|
function jailPath(base, filePath) {
|
|
30
31
|
if (!pathModule)
|
|
31
32
|
throw new Error('path module not available');
|
|
32
|
-
//
|
|
33
|
+
// Always check leading "/" or "\" first so the message is portable: POSIX
|
|
34
|
+
// absolute paths and Windows rooted paths both produce the same string.
|
|
35
|
+
const first = filePath.charCodeAt(0);
|
|
36
|
+
if (first === 0x2f /* / */ || first === 0x5c /* \ */) {
|
|
37
|
+
throw new Error(`SECURITY: rooted paths are not allowed: '${filePath}'`);
|
|
38
|
+
}
|
|
33
39
|
if (pathModule.isAbsolute(filePath)) {
|
|
34
|
-
throw new Error(`absolute
|
|
40
|
+
throw new Error(`SECURITY: absolute paths are not allowed: '${filePath}'`);
|
|
35
41
|
}
|
|
36
42
|
const resolved = pathModule.resolve(base, filePath);
|
|
37
43
|
const normalizedBase = pathModule.resolve(base);
|
|
38
|
-
|
|
44
|
+
if (filePath.includes('..') &&
|
|
45
|
+
!resolved.startsWith(normalizedBase + pathModule.sep) &&
|
|
46
|
+
resolved !== normalizedBase) {
|
|
47
|
+
throw new Error(`SECURITY: path traversal detected: '${filePath}'`);
|
|
48
|
+
}
|
|
39
49
|
if (!resolved.startsWith(normalizedBase + pathModule.sep) && resolved !== normalizedBase) {
|
|
40
|
-
throw new Error(`path escapes base directory: ${filePath}`);
|
|
50
|
+
throw new Error(`SECURITY: path escapes base directory: '${filePath}'`);
|
|
41
51
|
}
|
|
42
52
|
return resolved;
|
|
43
53
|
}
|
|
@@ -72,6 +82,20 @@ class SynxSecret {
|
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
const SPAM_BUCKETS = new Map();
|
|
85
|
+
/**
|
|
86
|
+
* Normalise Node.js fs error messages to OS-agnostic strings so that
|
|
87
|
+
* INCLUDE_ERR / WATCH_ERR text matches the Rust engine. Without this, the
|
|
88
|
+
* surface depends on the host OS ("ENOENT: no such file or directory" on Linux,
|
|
89
|
+
* "ENOENT…" + Windows variant) — diff-noisy and not portable.
|
|
90
|
+
*/
|
|
91
|
+
function fmtIoErr(err, ctx) {
|
|
92
|
+
const code = err && err.code;
|
|
93
|
+
if (code === 'ENOENT')
|
|
94
|
+
return `file not found: ${ctx}`;
|
|
95
|
+
if (code === 'EACCES' || code === 'EPERM')
|
|
96
|
+
return `permission denied: ${ctx}`;
|
|
97
|
+
return err && err.message ? err.message : String(err);
|
|
98
|
+
}
|
|
75
99
|
// ─── Engine ───────────────────────────────────────────────
|
|
76
100
|
function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _currentPath = '') {
|
|
77
101
|
if (!root) {
|
|
@@ -84,8 +108,11 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
84
108
|
delete obj[k];
|
|
85
109
|
}
|
|
86
110
|
// ── Load !include directives ──
|
|
87
|
-
if (!includesMap
|
|
88
|
-
|
|
111
|
+
if (!includesMap) {
|
|
112
|
+
const inc = options._includes;
|
|
113
|
+
includesMap = inc
|
|
114
|
+
? loadIncludes(inc, options)
|
|
115
|
+
: new Map();
|
|
89
116
|
}
|
|
90
117
|
}
|
|
91
118
|
// Guard: prevent stack overflow from deeply nested objects
|
|
@@ -140,10 +167,10 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
140
167
|
obj[key] = resolvedTarget;
|
|
141
168
|
}
|
|
142
169
|
}
|
|
143
|
-
// ── :include ──
|
|
144
|
-
if (markers.includes('include') && typeof obj[key] === 'string') {
|
|
170
|
+
// ── :include / :import ──
|
|
171
|
+
if ((markers.includes('include') || markers.includes('import')) && typeof obj[key] === 'string') {
|
|
145
172
|
if (!fs || !pathModule) {
|
|
146
|
-
obj[key] = 'INCLUDE_ERR: :include is not supported in browser';
|
|
173
|
+
obj[key] = 'INCLUDE_ERR: :include/:import is not supported in browser';
|
|
147
174
|
continue;
|
|
148
175
|
}
|
|
149
176
|
const maxDepth = options.maxIncludeDepth ?? DEFAULT_MAX_INCLUDE_DEPTH;
|
|
@@ -163,29 +190,36 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
163
190
|
resolve(included, { ...options, basePath: pathModule.dirname(fullPath), _includeDepth: currentDepth + 1 }, root);
|
|
164
191
|
}
|
|
165
192
|
obj[key] = included;
|
|
193
|
+
// Register the loaded subtree under the key as an alias so
|
|
194
|
+
// `{leaf:<key>}` interpolation finds it (parity with !include directives).
|
|
195
|
+
if (includesMap) {
|
|
196
|
+
includesMap.set(key, included);
|
|
197
|
+
}
|
|
166
198
|
}
|
|
167
199
|
catch (e) {
|
|
168
|
-
obj[key] = `INCLUDE_ERR: ${e
|
|
200
|
+
obj[key] = `INCLUDE_ERR: ${fmtIoErr(e, includePath)}`;
|
|
169
201
|
}
|
|
170
202
|
continue;
|
|
171
203
|
}
|
|
172
204
|
// ── :env ──
|
|
173
|
-
|
|
174
|
-
|
|
205
|
+
// Only resolve when the value is the string variable name. Mirrors the
|
|
206
|
+
// Rust engine's `if let Some(Value::String(var_name)) = map.get(key)` guard
|
|
207
|
+
// so that `key:env:default:foo` with no value (which is parsed as a group)
|
|
208
|
+
// keeps its `{}` shape instead of accidentally applying the default.
|
|
209
|
+
if (markers.includes('env') && typeof value === 'string') {
|
|
210
|
+
const varName = value;
|
|
175
211
|
const envSource = options.env || (typeof process !== 'undefined' ? process.env : {});
|
|
176
212
|
const envVal = envSource[varName];
|
|
177
|
-
// Check for :default in the marker chain
|
|
178
213
|
const defaultIdx = markers.indexOf('default');
|
|
179
|
-
// Check if key has (string) type hint — if so, skip auto-detection
|
|
180
214
|
const forceString = metaMap[key]?.typeHint === 'string';
|
|
181
215
|
if (envVal !== undefined && envVal !== '') {
|
|
182
|
-
obj[key] = forceString ? envVal : (
|
|
216
|
+
obj[key] = forceString ? envVal : castPrimitive(envVal);
|
|
183
217
|
}
|
|
184
218
|
else if (defaultIdx !== -1 && markers.length > defaultIdx + 1) {
|
|
185
219
|
// :env:default:VALUE — join all parts after 'default' back with ':'
|
|
186
220
|
// to preserve IPs (0.0.0.0) and compound values
|
|
187
221
|
const fallback = markers.slice(defaultIdx + 1).join(':');
|
|
188
|
-
obj[key] = forceString ? fallback : (
|
|
222
|
+
obj[key] = forceString ? fallback : castPrimitive(fallback);
|
|
189
223
|
}
|
|
190
224
|
else {
|
|
191
225
|
obj[key] = null;
|
|
@@ -241,12 +275,34 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
241
275
|
}
|
|
242
276
|
// ── :i18n ──
|
|
243
277
|
// Selects a localized value from a nested object based on options.lang.
|
|
244
|
-
//
|
|
278
|
+
// Plain syntax: name:i18n\n en Plains\n ru Равнины
|
|
279
|
+
// Plural syntax: title:i18n:item_count\n en\n one {count} item\n other {count} items\n ru\n one ...\n few ...\n many ...\n other ...
|
|
245
280
|
if (markers.includes('i18n') && obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
246
281
|
const translations = obj[key];
|
|
247
282
|
const lang = options.lang || 'en';
|
|
248
|
-
|
|
249
|
-
|
|
283
|
+
let val = translations[lang] ?? translations['en'] ?? Object.values(translations)[0] ?? null;
|
|
284
|
+
const i18nIdx = markers.indexOf('i18n');
|
|
285
|
+
const countField = markers[i18nIdx + 1];
|
|
286
|
+
// Pluralisation: when the picked language entry is itself an object
|
|
287
|
+
// (one/few/many/other), choose by count.
|
|
288
|
+
if (countField && val && typeof val === 'object' && !Array.isArray(val)) {
|
|
289
|
+
const pluralForms = val;
|
|
290
|
+
const countRaw = (deepGet(root, countField) ?? deepGet(obj, countField));
|
|
291
|
+
const count = typeof countRaw === 'number' ? countRaw
|
|
292
|
+
: typeof countRaw === 'string' && !isNaN(Number(countRaw)) ? Number(countRaw)
|
|
293
|
+
: 0;
|
|
294
|
+
const category = pluralCategory(lang, Math.trunc(count));
|
|
295
|
+
const chosen = pluralForms[category] ?? pluralForms['other'] ?? Object.values(pluralForms)[0] ?? null;
|
|
296
|
+
if (typeof chosen === 'string') {
|
|
297
|
+
obj[key] = chosen.split('{count}').join(String(Math.trunc(count)));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
obj[key] = chosen;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
obj[key] = val;
|
|
305
|
+
}
|
|
250
306
|
}
|
|
251
307
|
// ── :calc ──
|
|
252
308
|
if (markers.includes('calc') && typeof obj[key] === 'string') {
|
|
@@ -271,6 +327,8 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
271
327
|
// Substitute whole-word occurrences without building RegExp objects
|
|
272
328
|
if (vars.size > 0)
|
|
273
329
|
expr = replaceVars(expr, vars);
|
|
330
|
+
// Substitute dot-path references (e.g. base.hp, server.port) before evaluating.
|
|
331
|
+
expr = replaceDotPaths(expr, root);
|
|
274
332
|
try {
|
|
275
333
|
obj[key] = (0, calc_1.safeCalc)(expr);
|
|
276
334
|
}
|
|
@@ -305,7 +363,11 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
305
363
|
const targetHasAlias = targetParentMeta?.[targetLeafKey]?.markers?.includes('alias') ?? false;
|
|
306
364
|
const isCycle = targetHasAlias && typeof targetVal === 'string' && targetVal === key;
|
|
307
365
|
if (isCycle) {
|
|
308
|
-
|
|
366
|
+
// Stable, order-independent message: sort participants lexicographically
|
|
367
|
+
// so both keys produce the same string regardless of iteration order.
|
|
368
|
+
const a = currentKeyPath <= target ? currentKeyPath : target;
|
|
369
|
+
const b = currentKeyPath <= target ? target : currentKeyPath;
|
|
370
|
+
obj[key] = `ALIAS_ERR: circular alias detected: ${a} → ${b}`;
|
|
309
371
|
}
|
|
310
372
|
else {
|
|
311
373
|
obj[key] = targetVal ?? null;
|
|
@@ -368,7 +430,7 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
368
430
|
if (defaultIdx !== -1 && markers.length > defaultIdx + 1) {
|
|
369
431
|
const fallback = markers.slice(defaultIdx + 1).join(':');
|
|
370
432
|
const forceStr = metaMap[key]?.typeHint === 'string';
|
|
371
|
-
obj[key] = forceStr ? fallback : (
|
|
433
|
+
obj[key] = forceStr ? fallback : castPrimitive(fallback);
|
|
372
434
|
}
|
|
373
435
|
}
|
|
374
436
|
}
|
|
@@ -420,6 +482,54 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
420
482
|
const pattern = markers[idx + 1] ?? '%s';
|
|
421
483
|
obj[key] = applyFormatPattern(pattern, obj[key]);
|
|
422
484
|
}
|
|
485
|
+
// ── :replace:FROM:TO ── @since 3.6.2
|
|
486
|
+
// Literal substring replacement on a string value. `to` defaults to "" (deletion).
|
|
487
|
+
// Limitation: `from` and `to` cannot themselves contain `:` since the marker
|
|
488
|
+
// chain is colon-delimited; for those cases use {interpolation} instead.
|
|
489
|
+
if (markers.includes('replace') && typeof obj[key] === 'string') {
|
|
490
|
+
const idx = markers.indexOf('replace');
|
|
491
|
+
const from = markers[idx + 1];
|
|
492
|
+
const to = markers[idx + 2] ?? '';
|
|
493
|
+
if (from !== undefined && from !== '') {
|
|
494
|
+
obj[key] = obj[key].split(from).join(to);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// ── :sort / :sort:desc ── @since 3.6.2
|
|
498
|
+
// Sort an array. Default direction is ascending; `:sort:desc` reverses.
|
|
499
|
+
// Numbers compare numerically; mixed/string entries fall back to localeCompare.
|
|
500
|
+
if (markers.includes('sort') && Array.isArray(obj[key])) {
|
|
501
|
+
const idx = markers.indexOf('sort');
|
|
502
|
+
const dir = markers[idx + 1];
|
|
503
|
+
const sorted = obj[key].slice().sort((a, b) => {
|
|
504
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
505
|
+
return a - b;
|
|
506
|
+
return String(a).localeCompare(String(b));
|
|
507
|
+
});
|
|
508
|
+
if (dir === 'desc')
|
|
509
|
+
sorted.reverse();
|
|
510
|
+
obj[key] = sorted;
|
|
511
|
+
}
|
|
512
|
+
// ── :sum ── @since 3.6.2
|
|
513
|
+
// Sum the numeric items of an array. Non-numeric entries are ignored.
|
|
514
|
+
// Returns an integer when all summands are integers, otherwise a float.
|
|
515
|
+
if (markers.includes('sum') && Array.isArray(obj[key])) {
|
|
516
|
+
let total = 0;
|
|
517
|
+
let allInt = true;
|
|
518
|
+
for (const v of obj[key]) {
|
|
519
|
+
if (typeof v === 'number') {
|
|
520
|
+
total += v;
|
|
521
|
+
if (!Number.isInteger(v))
|
|
522
|
+
allInt = false;
|
|
523
|
+
}
|
|
524
|
+
else if (typeof v === 'string' && v !== '' && !isNaN(Number(v))) {
|
|
525
|
+
const n = Number(v);
|
|
526
|
+
total += n;
|
|
527
|
+
if (!Number.isInteger(n))
|
|
528
|
+
allInt = false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
obj[key] = allInt ? Math.trunc(total) : total;
|
|
532
|
+
}
|
|
423
533
|
// ── :fallback ──
|
|
424
534
|
// Syntax: key:fallback:DEFAULT_PATH value
|
|
425
535
|
// If value is empty OR file does not exist, use the fallback.
|
|
@@ -511,7 +621,7 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
|
|
|
511
621
|
}
|
|
512
622
|
}
|
|
513
623
|
catch (e) {
|
|
514
|
-
obj[key] = `WATCH_ERR: ${e
|
|
624
|
+
obj[key] = `WATCH_ERR: ${fmtIoErr(e, filePath)}`;
|
|
515
625
|
}
|
|
516
626
|
}
|
|
517
627
|
}
|
|
@@ -555,8 +665,11 @@ function applyInheritance(obj) {
|
|
|
555
665
|
const meta = metaMap[key];
|
|
556
666
|
if (!meta || !meta.markers.includes('inherit'))
|
|
557
667
|
continue;
|
|
668
|
+
// Two supported syntaxes:
|
|
669
|
+
// production:inherit:base → "base" is markers[idx+1]
|
|
670
|
+
// production:inherit base → "base" is meta.args[0] (parser promotes value into args)
|
|
558
671
|
const idx = meta.markers.indexOf('inherit');
|
|
559
|
-
const parentName = meta.markers[idx + 1];
|
|
672
|
+
const parentName = meta.markers[idx + 1] || (meta.args && meta.args[0]);
|
|
560
673
|
if (!parentName)
|
|
561
674
|
continue;
|
|
562
675
|
const parentObj = obj[parentName];
|
|
@@ -817,7 +930,9 @@ function extractFromFileContent(content, keyPath, ext) {
|
|
|
817
930
|
}
|
|
818
931
|
}
|
|
819
932
|
// ─── Helpers ──────────────────────────────────────────────
|
|
820
|
-
/** Serialize a value to SYNX format string (for :prompt marker).
|
|
933
|
+
/** Serialize a value to SYNX format string (for :prompt marker).
|
|
934
|
+
* Keys are emitted in sorted order so that LLM prompts are deterministic
|
|
935
|
+
* across runs and across language bindings (matches the Rust engine). */
|
|
821
936
|
function stringifyValue(value, indent) {
|
|
822
937
|
const sp = ' '.repeat(indent);
|
|
823
938
|
if (value === null || value === undefined)
|
|
@@ -834,9 +949,10 @@ function stringifyValue(value, indent) {
|
|
|
834
949
|
}
|
|
835
950
|
if (typeof value === 'object') {
|
|
836
951
|
let out = '';
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
952
|
+
const obj = value;
|
|
953
|
+
const keys = Object.keys(obj).filter(k => k !== '__synx').sort();
|
|
954
|
+
for (const k of keys) {
|
|
955
|
+
const v = obj[k];
|
|
840
956
|
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
841
957
|
out += `${sp}${k}\n`;
|
|
842
958
|
out += stringifyValue(v, indent + 2);
|
|
@@ -867,6 +983,56 @@ function castPrimitive(val) {
|
|
|
867
983
|
return parseFloat(val);
|
|
868
984
|
return val;
|
|
869
985
|
}
|
|
986
|
+
/**
|
|
987
|
+
* CLDR-inspired plural category selection.
|
|
988
|
+
* Mirrors the Rust `plural_category` table in `synx-core::engine`.
|
|
989
|
+
*/
|
|
990
|
+
function pluralCategory(lang, n) {
|
|
991
|
+
const absN = Math.abs(n);
|
|
992
|
+
const n10 = absN % 10;
|
|
993
|
+
const n100 = absN % 100;
|
|
994
|
+
switch (lang) {
|
|
995
|
+
case 'ru':
|
|
996
|
+
case 'uk':
|
|
997
|
+
case 'be':
|
|
998
|
+
case 'pl':
|
|
999
|
+
if (n10 === 1 && n100 !== 11)
|
|
1000
|
+
return 'one';
|
|
1001
|
+
if (n10 >= 2 && n10 <= 4 && !(n100 >= 12 && n100 <= 14))
|
|
1002
|
+
return 'few';
|
|
1003
|
+
return 'many';
|
|
1004
|
+
case 'cs':
|
|
1005
|
+
case 'sk':
|
|
1006
|
+
if (absN === 1)
|
|
1007
|
+
return 'one';
|
|
1008
|
+
if (absN >= 2 && absN <= 4)
|
|
1009
|
+
return 'few';
|
|
1010
|
+
return 'other';
|
|
1011
|
+
case 'ar':
|
|
1012
|
+
if (absN === 0)
|
|
1013
|
+
return 'zero';
|
|
1014
|
+
if (absN === 1)
|
|
1015
|
+
return 'one';
|
|
1016
|
+
if (absN === 2)
|
|
1017
|
+
return 'two';
|
|
1018
|
+
if (n100 >= 3 && n100 <= 10)
|
|
1019
|
+
return 'few';
|
|
1020
|
+
if (n100 >= 11 && n100 <= 99)
|
|
1021
|
+
return 'many';
|
|
1022
|
+
return 'other';
|
|
1023
|
+
case 'fr':
|
|
1024
|
+
case 'pt':
|
|
1025
|
+
return absN <= 1 ? 'one' : 'other';
|
|
1026
|
+
case 'ja':
|
|
1027
|
+
case 'zh':
|
|
1028
|
+
case 'ko':
|
|
1029
|
+
case 'vi':
|
|
1030
|
+
case 'th':
|
|
1031
|
+
return 'other';
|
|
1032
|
+
default:
|
|
1033
|
+
return absN === 1 ? 'one' : 'other';
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
870
1036
|
const DELIM_MAP = {
|
|
871
1037
|
space: ' ', pipe: '|', dash: '-', dot: '.', semi: ';', tab: '\t', slash: '/',
|
|
872
1038
|
};
|
|
@@ -935,6 +1101,50 @@ function replaceVars(expr, vars) {
|
|
|
935
1101
|
result = replaceWord(result, k, v);
|
|
936
1102
|
return result;
|
|
937
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Substitute dot-path references (`a.b.c`) in a calc expression with their
|
|
1106
|
+
* numeric values from the root tree. Tokens without a dot are left unchanged.
|
|
1107
|
+
* Matches the Rust `dot_resolved` pass in `synx-core::engine`.
|
|
1108
|
+
*/
|
|
1109
|
+
function replaceDotPaths(expr, root) {
|
|
1110
|
+
let out = '';
|
|
1111
|
+
let i = 0;
|
|
1112
|
+
const len = expr.length;
|
|
1113
|
+
while (i < len) {
|
|
1114
|
+
const code = expr.charCodeAt(i);
|
|
1115
|
+
if (isWordChar(code)) {
|
|
1116
|
+
const start = i;
|
|
1117
|
+
let hasDot = false;
|
|
1118
|
+
while (i < len) {
|
|
1119
|
+
const c = expr.charCodeAt(i);
|
|
1120
|
+
if (isWordChar(c)) {
|
|
1121
|
+
i++;
|
|
1122
|
+
}
|
|
1123
|
+
else if (c === 46) { // '.'
|
|
1124
|
+
hasDot = true;
|
|
1125
|
+
i++;
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const token = expr.substring(start, i);
|
|
1132
|
+
if (hasDot && token.indexOf('.') !== -1) {
|
|
1133
|
+
const val = deepGet(root, token);
|
|
1134
|
+
if (typeof val === 'number') {
|
|
1135
|
+
out += String(val);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
out += token;
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
out += expr[i];
|
|
1143
|
+
i++;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return out;
|
|
1147
|
+
}
|
|
938
1148
|
function allowSpamAccess(bucketKey, maxCalls, windowSec) {
|
|
939
1149
|
const now = Date.now();
|
|
940
1150
|
const windowMs = windowSec * 1000;
|