@neocompose/cli 0.34.2 → 0.35.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 +33 -0
- package/dist/neo.mjs +522 -108
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +3 -4
- package/skills/neocompose-cli/references/animation-and-world-authoring.md +11 -5
- package/skills/neocompose-cli/references/cli-development.md +1 -1
- package/skills/neocompose-cli/references/neoscript.md +44 -0
- package/skills/neocompose-cli/references/values-identities-and-references.md +1 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.35.0] - 2026-08-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- P72 expression bodies: write `=> expr;` wherever a body is a single
|
|
8
|
+
expression — getter-only properties (`bool HasVisited => this.Count > 0;`),
|
|
9
|
+
an accessor inside a property block (`get => ...;`, `set(int value) => ...;`),
|
|
10
|
+
functions (`bool Ready(int level) => this.Level >= level;`), and lambdas
|
|
11
|
+
(`(x) => x * 2`). It is pure sugar: the desugared block compiles to the same
|
|
12
|
+
instruction stream, and diagnostics point at the authored expression. In a
|
|
13
|
+
void context the expression is a statement, so it must call or assign; an
|
|
14
|
+
expression whose value would be discarded is refused with a diagnostic that
|
|
15
|
+
names the block body as the fix.
|
|
16
|
+
- The `=>` reads as an expression body only where it directly follows a member
|
|
17
|
+
name or a parameter list's `)`, so a NeoFlow destination arrow after an `=`
|
|
18
|
+
initializer (`override Trigger Trigger = new(...) => Welcome;`) is unchanged.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `neo pull` and `neo push` canonicalize a stored body to `=> expr;` when its
|
|
23
|
+
authored spelling is exactly one `return` statement that fits on one line;
|
|
24
|
+
every other body, and every setter, re-emits as a block. A push preserves the
|
|
25
|
+
form already on disk for members it did not otherwise rewrite, so authoring a
|
|
26
|
+
one-line getter in longhand stays byte-identical through a push.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- VS Code highlighting no longer claims the identifier after every `=>` as a
|
|
31
|
+
NeoFlow destination. The destination scope now requires the NeoFlow shape —
|
|
32
|
+
an arrow after `)`, a single non-keyword identifier, then `;` — so calls,
|
|
33
|
+
`this.` chains, types, and `get`/`set` keywords inside expression bodies keep
|
|
34
|
+
their own scopes.
|
|
35
|
+
|
|
3
36
|
## [0.34.2] - 2026-08-19
|
|
4
37
|
|
|
5
38
|
### Fixed
|
package/dist/neo.mjs
CHANGED
|
@@ -3293,7 +3293,13 @@ function insideLambdaBlock(tokens, catchBodyStart, offset) {
|
|
|
3293
3293
|
continue;
|
|
3294
3294
|
}
|
|
3295
3295
|
const open = significant[index + 1];
|
|
3296
|
-
if (open
|
|
3296
|
+
if (open === void 0) continue;
|
|
3297
|
+
if (open.kind !== "punctuation" || open.text !== "{") {
|
|
3298
|
+
if (open.start <= offset && offset <= lambdaExpressionEnd(significant, index + 1)) {
|
|
3299
|
+
return true;
|
|
3300
|
+
}
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3297
3303
|
const closeIndex = matchingCloseBraceIndex(significant, index + 1);
|
|
3298
3304
|
const close = closeIndex === null ? void 0 : significant[closeIndex];
|
|
3299
3305
|
if (open.end <= offset && (close === void 0 || offset <= close.start)) {
|
|
@@ -3302,6 +3308,23 @@ function insideLambdaBlock(tokens, catchBodyStart, offset) {
|
|
|
3302
3308
|
}
|
|
3303
3309
|
return false;
|
|
3304
3310
|
}
|
|
3311
|
+
function lambdaExpressionEnd(tokens, firstIndex) {
|
|
3312
|
+
let depth = 0;
|
|
3313
|
+
let end = tokens[firstIndex]?.end ?? 0;
|
|
3314
|
+
for (let index = firstIndex; index < tokens.length; index++) {
|
|
3315
|
+
const token = tokens[index];
|
|
3316
|
+
if (!token) break;
|
|
3317
|
+
if (token.kind === "punctuation") {
|
|
3318
|
+
if (["(", "[", "{"].includes(token.text)) depth++;
|
|
3319
|
+
else if ([")", "]", "}"].includes(token.text)) {
|
|
3320
|
+
if (depth === 0) return end;
|
|
3321
|
+
depth--;
|
|
3322
|
+
} else if (token.text === ";" && depth === 0) return end;
|
|
3323
|
+
}
|
|
3324
|
+
end = token.end;
|
|
3325
|
+
}
|
|
3326
|
+
return end;
|
|
3327
|
+
}
|
|
3305
3328
|
function matchingCloseBraceIndex(tokens, openIndex) {
|
|
3306
3329
|
let depth = 0;
|
|
3307
3330
|
for (let index = openIndex; index < tokens.length; index++) {
|
|
@@ -5076,14 +5099,16 @@ function parseUnits(tokens, pairs, kind, lexed) {
|
|
|
5076
5099
|
}
|
|
5077
5100
|
];
|
|
5078
5101
|
}
|
|
5102
|
+
const shorthand = propertyShorthandUnit(tokens, pairs, lexed);
|
|
5103
|
+
if (shorthand) return [shorthand];
|
|
5079
5104
|
const units = [];
|
|
5080
5105
|
const candidates = [];
|
|
5081
5106
|
for (let index = 0; index < tokens.length; index++) {
|
|
5082
5107
|
const token = tokens[index];
|
|
5083
5108
|
if (!token) continue;
|
|
5084
5109
|
if (token.kind === "identifier" && (token.text === "get" || token.text === "set") && isAccessorHeader(token, lexed)) {
|
|
5085
|
-
const
|
|
5086
|
-
if (
|
|
5110
|
+
const open = accessorBodyOpen(tokens, pairs, index);
|
|
5111
|
+
if (open) candidates.push({ tokenIndex: index, ...open });
|
|
5087
5112
|
}
|
|
5088
5113
|
}
|
|
5089
5114
|
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
|
|
@@ -5093,6 +5118,18 @@ function parseUnits(tokens, pairs, kind, lexed) {
|
|
|
5093
5118
|
const open = tokens[candidate.openIndex];
|
|
5094
5119
|
if (!token || !open) continue;
|
|
5095
5120
|
const nextCandidate = candidates[candidateIndex + 1];
|
|
5121
|
+
if (candidate.expressionBody) {
|
|
5122
|
+
const unit = expressionBodyUnit(
|
|
5123
|
+
tokens,
|
|
5124
|
+
pairs,
|
|
5125
|
+
lexed,
|
|
5126
|
+
token,
|
|
5127
|
+
candidate.openIndex,
|
|
5128
|
+
nextCandidate?.tokenIndex ?? tokens.length
|
|
5129
|
+
);
|
|
5130
|
+
if (unit) units.push(unit);
|
|
5131
|
+
continue;
|
|
5132
|
+
}
|
|
5096
5133
|
const protectedCloseIndex = findProtectedRegionClose(
|
|
5097
5134
|
tokens,
|
|
5098
5135
|
candidate.openIndex,
|
|
@@ -5122,15 +5159,70 @@ function isAccessorHeader(token, lexed) {
|
|
|
5122
5159
|
const line = lexed.source.lineText(token.range.start.line);
|
|
5123
5160
|
return line.slice(0, token.range.start.character).trim().length === 0;
|
|
5124
5161
|
}
|
|
5125
|
-
function
|
|
5126
|
-
|
|
5127
|
-
if (
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
const
|
|
5133
|
-
|
|
5162
|
+
function accessorBodyOpen(tokens, pairs, accessorIndex) {
|
|
5163
|
+
let openIndex = accessorIndex + 1;
|
|
5164
|
+
if (tokens[openIndex]?.text === "(") {
|
|
5165
|
+
const closeParen = pairs.get(openIndex);
|
|
5166
|
+
if (closeParen === void 0) return null;
|
|
5167
|
+
openIndex = closeParen + 1;
|
|
5168
|
+
}
|
|
5169
|
+
const open = tokens[openIndex];
|
|
5170
|
+
if (open?.kind === "punctuation" && open.text === "{") {
|
|
5171
|
+
return { openIndex, expressionBody: false };
|
|
5172
|
+
}
|
|
5173
|
+
if (open?.kind === "operator" && open.text === "=>") {
|
|
5174
|
+
return { openIndex, expressionBody: true };
|
|
5175
|
+
}
|
|
5176
|
+
return null;
|
|
5177
|
+
}
|
|
5178
|
+
function expressionBodyUnit(tokens, pairs, lexed, keyword, arrowIndex, limit) {
|
|
5179
|
+
const first = tokens[arrowIndex + 1];
|
|
5180
|
+
if (!first || first.text === ";") return null;
|
|
5181
|
+
const end = expressionBodyEnd(tokens, pairs, arrowIndex + 1, limit);
|
|
5182
|
+
if (end.bodyEnd <= first.start) return null;
|
|
5183
|
+
return {
|
|
5184
|
+
kind: keyword.text === "get" ? "getter" : "setter",
|
|
5185
|
+
range: lexed.source.range(keyword.start, end.unitEnd),
|
|
5186
|
+
bodyRange: lexed.source.range(first.start, end.bodyEnd),
|
|
5187
|
+
bodyStart: first.start,
|
|
5188
|
+
bodyEnd: end.bodyEnd,
|
|
5189
|
+
expressionBody: true
|
|
5190
|
+
};
|
|
5191
|
+
}
|
|
5192
|
+
function propertyShorthandUnit(tokens, pairs, lexed) {
|
|
5193
|
+
const arrow = tokens[0];
|
|
5194
|
+
if (arrow?.kind !== "operator" || arrow.text !== "=>") return null;
|
|
5195
|
+
const first = tokens[1];
|
|
5196
|
+
if (!first) return null;
|
|
5197
|
+
const end = expressionBodyEnd(tokens, pairs, 1, tokens.length);
|
|
5198
|
+
if (end.bodyEnd <= first.start) return null;
|
|
5199
|
+
return {
|
|
5200
|
+
kind: "getter",
|
|
5201
|
+
range: lexed.source.range(arrow.start, end.unitEnd),
|
|
5202
|
+
bodyRange: lexed.source.range(first.start, end.bodyEnd),
|
|
5203
|
+
bodyStart: first.start,
|
|
5204
|
+
bodyEnd: end.bodyEnd,
|
|
5205
|
+
expressionBody: true
|
|
5206
|
+
};
|
|
5207
|
+
}
|
|
5208
|
+
function expressionBodyEnd(tokens, pairs, firstIndex, limit) {
|
|
5209
|
+
let last = tokens[firstIndex];
|
|
5210
|
+
for (let cursor = firstIndex; cursor < limit; cursor++) {
|
|
5211
|
+
const token = tokens[cursor];
|
|
5212
|
+
if (!token) break;
|
|
5213
|
+
if (token.text === ";") {
|
|
5214
|
+
return { bodyEnd: last.end, unitEnd: token.end };
|
|
5215
|
+
}
|
|
5216
|
+
if (token.text === "}") return { bodyEnd: last.end, unitEnd: last.end };
|
|
5217
|
+
const close = OPEN_TO_CLOSE.has(token.text) ? pairs.get(cursor) : void 0;
|
|
5218
|
+
if (close !== void 0 && close < limit) {
|
|
5219
|
+
cursor = close;
|
|
5220
|
+
last = tokens[close] ?? last;
|
|
5221
|
+
continue;
|
|
5222
|
+
}
|
|
5223
|
+
last = token;
|
|
5224
|
+
}
|
|
5225
|
+
return { bodyEnd: last.end, unitEnd: last.end };
|
|
5134
5226
|
}
|
|
5135
5227
|
function findProtectedRegionClose(tokens, openIndex, indentation, limit) {
|
|
5136
5228
|
for (let index = openIndex + 1; index < limit; index++) {
|
|
@@ -5386,8 +5478,8 @@ function validatePropertyUnits(units, diagnostics, lexed) {
|
|
|
5386
5478
|
severity: "error",
|
|
5387
5479
|
source: "neoscript",
|
|
5388
5480
|
code: "missing-getter",
|
|
5389
|
-
message: "A NeoScript property sidecar must declare one `get { ... }` unit.",
|
|
5390
|
-
suggestions: ["Add `get { return ...; }`."]
|
|
5481
|
+
message: "A NeoScript property sidecar must declare one `get { ... }` or `get => ...;` unit.",
|
|
5482
|
+
suggestions: ["Add `get => ...;` or `get { return ...; }`."]
|
|
5391
5483
|
});
|
|
5392
5484
|
}
|
|
5393
5485
|
if (getters.length > 1) duplicateUnitDiagnostic("get", getters, diagnostics);
|
|
@@ -5933,6 +6025,60 @@ var init_strict_compile_error = __esm({
|
|
|
5933
6025
|
}
|
|
5934
6026
|
});
|
|
5935
6027
|
|
|
6028
|
+
// ../packages/neoscript-language/src/strict-ast.ts
|
|
6029
|
+
function expressionBodyStatements(body, voidContext) {
|
|
6030
|
+
const pos = body.start;
|
|
6031
|
+
if (body.assignment) {
|
|
6032
|
+
return [
|
|
6033
|
+
{
|
|
6034
|
+
kind: "assign",
|
|
6035
|
+
target: body.expr,
|
|
6036
|
+
op: body.assignment.op,
|
|
6037
|
+
value: body.assignment.value,
|
|
6038
|
+
pos
|
|
6039
|
+
}
|
|
6040
|
+
];
|
|
6041
|
+
}
|
|
6042
|
+
if (voidContext) {
|
|
6043
|
+
return [{ kind: "exprStmt", expr: body.expr, pos, expressionBody: true }];
|
|
6044
|
+
}
|
|
6045
|
+
return [{ kind: "return", expr: body.expr, pos }];
|
|
6046
|
+
}
|
|
6047
|
+
function collectReturnStatements(statements) {
|
|
6048
|
+
const result = [];
|
|
6049
|
+
for (const statement of statements) {
|
|
6050
|
+
if (statement.kind === "return") result.push(statement);
|
|
6051
|
+
if (statement.kind === "if") {
|
|
6052
|
+
for (const branch of statement.branches) {
|
|
6053
|
+
result.push(...collectReturnStatements(branch.body));
|
|
6054
|
+
}
|
|
6055
|
+
if (statement.elseBody) {
|
|
6056
|
+
result.push(...collectReturnStatements(statement.elseBody));
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
if (statement.kind === "for" || statement.kind === "forEach") {
|
|
6060
|
+
result.push(...collectReturnStatements(statement.body));
|
|
6061
|
+
}
|
|
6062
|
+
if (statement.kind === "switch") {
|
|
6063
|
+
for (const section of statement.sections) {
|
|
6064
|
+
result.push(...collectReturnStatements(section.body));
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
if (statement.kind === "try") {
|
|
6068
|
+
result.push(...collectReturnStatements(statement.body));
|
|
6069
|
+
for (const clause of statement.catches) {
|
|
6070
|
+
result.push(...collectReturnStatements(clause.body));
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
return result;
|
|
6075
|
+
}
|
|
6076
|
+
var init_strict_ast = __esm({
|
|
6077
|
+
"../packages/neoscript-language/src/strict-ast.ts"() {
|
|
6078
|
+
"use strict";
|
|
6079
|
+
}
|
|
6080
|
+
});
|
|
6081
|
+
|
|
5936
6082
|
// ../packages/neoscript-language/src/project-source-identifiers.ts
|
|
5937
6083
|
function isValidNeoProjectIdentifier(name) {
|
|
5938
6084
|
return PROJECT_IDENTIFIER_PATTERN.test(name) && !PROJECT_RESERVED_NAMES.has(name);
|
|
@@ -6486,6 +6632,17 @@ function parseFunctionBody(source) {
|
|
|
6486
6632
|
pos: tokens[0]?.pos ?? { line: 1, column: 1 }
|
|
6487
6633
|
};
|
|
6488
6634
|
}
|
|
6635
|
+
function parseExpressionBody(source) {
|
|
6636
|
+
const tokens = lex2(source);
|
|
6637
|
+
const parser = new Parser(tokens);
|
|
6638
|
+
const expressionBody = parser.parseExpressionBodyTail();
|
|
6639
|
+
parser.expect("eof");
|
|
6640
|
+
return {
|
|
6641
|
+
body: expressionBodyStatements(expressionBody, false),
|
|
6642
|
+
pos: expressionBody.start,
|
|
6643
|
+
expressionBody
|
|
6644
|
+
};
|
|
6645
|
+
}
|
|
6489
6646
|
function parseExpression(source) {
|
|
6490
6647
|
const parser = new Parser(lex2(source));
|
|
6491
6648
|
const expression = parser.parseExpr();
|
|
@@ -6535,6 +6692,7 @@ var Parser;
|
|
|
6535
6692
|
var init_strict_parser = __esm({
|
|
6536
6693
|
"../packages/neoscript-language/src/strict-parser.ts"() {
|
|
6537
6694
|
"use strict";
|
|
6695
|
+
init_strict_ast();
|
|
6538
6696
|
init_strict_compile_error();
|
|
6539
6697
|
init_language_spec();
|
|
6540
6698
|
init_project_source_identifiers();
|
|
@@ -7651,6 +7809,16 @@ var init_strict_parser = __esm({
|
|
|
7651
7809
|
}
|
|
7652
7810
|
this.expect("punct", ")");
|
|
7653
7811
|
this.expect("op", "=>");
|
|
7812
|
+
if (!(this.peek().kind === "punct" && this.peek().text === "{")) {
|
|
7813
|
+
const expressionBody = this.parseExpressionBodyTail();
|
|
7814
|
+
return {
|
|
7815
|
+
kind: "lambda",
|
|
7816
|
+
params,
|
|
7817
|
+
body: expressionBodyStatements(expressionBody, false),
|
|
7818
|
+
pos: startPos,
|
|
7819
|
+
expressionBody
|
|
7820
|
+
};
|
|
7821
|
+
}
|
|
7654
7822
|
this.expect("punct", "{");
|
|
7655
7823
|
const body = this.parseStatementList(false);
|
|
7656
7824
|
this.expect("punct", "}");
|
|
@@ -7661,6 +7829,46 @@ var init_strict_parser = __esm({
|
|
|
7661
7829
|
pos: startPos
|
|
7662
7830
|
};
|
|
7663
7831
|
}
|
|
7832
|
+
/**
|
|
7833
|
+
* Parses the body of an expression-bodied lambda or member — everything
|
|
7834
|
+
* after `=>`, stopping before any terminator the caller owns.
|
|
7835
|
+
*
|
|
7836
|
+
* Assignment and increment operators are accepted here because NeoScript
|
|
7837
|
+
* models them as statements rather than expressions; that is what makes
|
|
7838
|
+
* `set(int value) => _field = value` expressible. The resolver rejects the
|
|
7839
|
+
* assignment form wherever the unit has to produce a value.
|
|
7840
|
+
*/
|
|
7841
|
+
parseExpressionBodyTail() {
|
|
7842
|
+
const start = this.peek().pos;
|
|
7843
|
+
if (isIncrementOp(this.peek())) {
|
|
7844
|
+
const opTok = this.next();
|
|
7845
|
+
const target = this.parseExpr();
|
|
7846
|
+
return this.expressionBody(start, target, {
|
|
7847
|
+
op: opTok.text,
|
|
7848
|
+
value: null
|
|
7849
|
+
});
|
|
7850
|
+
}
|
|
7851
|
+
const expr = this.parseExpr();
|
|
7852
|
+
if (isIncrementOp(this.peek())) {
|
|
7853
|
+
const opTok = this.next();
|
|
7854
|
+
return this.expressionBody(start, expr, {
|
|
7855
|
+
op: opTok.text,
|
|
7856
|
+
value: null
|
|
7857
|
+
});
|
|
7858
|
+
}
|
|
7859
|
+
if (isAssignmentOp(this.peek())) {
|
|
7860
|
+
const opTok = this.next();
|
|
7861
|
+
return this.expressionBody(start, expr, {
|
|
7862
|
+
op: opTok.text,
|
|
7863
|
+
value: this.parseExpr()
|
|
7864
|
+
});
|
|
7865
|
+
}
|
|
7866
|
+
return this.expressionBody(start, expr, null);
|
|
7867
|
+
}
|
|
7868
|
+
/** Closes an expression body at the token the parser has not consumed. */
|
|
7869
|
+
expressionBody(start, expr, assignment) {
|
|
7870
|
+
return { expr, assignment, start, end: this.peek().pos };
|
|
7871
|
+
}
|
|
7664
7872
|
/**
|
|
7665
7873
|
* Parses a single lambda parameter. Supports:
|
|
7666
7874
|
* - `name` (untyped — resolver infers from receiver)
|
|
@@ -7738,42 +7946,6 @@ var init_strict_parser = __esm({
|
|
|
7738
7946
|
}
|
|
7739
7947
|
});
|
|
7740
7948
|
|
|
7741
|
-
// ../packages/neoscript-language/src/strict-ast.ts
|
|
7742
|
-
function collectReturnStatements(statements) {
|
|
7743
|
-
const result = [];
|
|
7744
|
-
for (const statement of statements) {
|
|
7745
|
-
if (statement.kind === "return") result.push(statement);
|
|
7746
|
-
if (statement.kind === "if") {
|
|
7747
|
-
for (const branch of statement.branches) {
|
|
7748
|
-
result.push(...collectReturnStatements(branch.body));
|
|
7749
|
-
}
|
|
7750
|
-
if (statement.elseBody) {
|
|
7751
|
-
result.push(...collectReturnStatements(statement.elseBody));
|
|
7752
|
-
}
|
|
7753
|
-
}
|
|
7754
|
-
if (statement.kind === "for" || statement.kind === "forEach") {
|
|
7755
|
-
result.push(...collectReturnStatements(statement.body));
|
|
7756
|
-
}
|
|
7757
|
-
if (statement.kind === "switch") {
|
|
7758
|
-
for (const section of statement.sections) {
|
|
7759
|
-
result.push(...collectReturnStatements(section.body));
|
|
7760
|
-
}
|
|
7761
|
-
}
|
|
7762
|
-
if (statement.kind === "try") {
|
|
7763
|
-
result.push(...collectReturnStatements(statement.body));
|
|
7764
|
-
for (const clause of statement.catches) {
|
|
7765
|
-
result.push(...collectReturnStatements(clause.body));
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
}
|
|
7769
|
-
return result;
|
|
7770
|
-
}
|
|
7771
|
-
var init_strict_ast = __esm({
|
|
7772
|
-
"../packages/neoscript-language/src/strict-ast.ts"() {
|
|
7773
|
-
"use strict";
|
|
7774
|
-
}
|
|
7775
|
-
});
|
|
7776
|
-
|
|
7777
7949
|
// ../packages/neoscript-language/src/declared-constructors.ts
|
|
7778
7950
|
function neoScriptArgumentNameSetKey(names) {
|
|
7779
7951
|
return [...names].map((name) => name.toLowerCase()).sort().join(",");
|
|
@@ -9095,6 +9267,9 @@ function typesOverlap(left, right, project) {
|
|
|
9095
9267
|
function isUnknown(type) {
|
|
9096
9268
|
return type.kind === "primitive" && type.name === "unknown";
|
|
9097
9269
|
}
|
|
9270
|
+
function describeUnitKind(kind) {
|
|
9271
|
+
return kind === "function" ? "NSFunction" : kind;
|
|
9272
|
+
}
|
|
9098
9273
|
function isVoid(type) {
|
|
9099
9274
|
return type?.kind === "primitive" && type.name === "void";
|
|
9100
9275
|
}
|
|
@@ -9769,7 +9944,12 @@ var init_strict_resolver = __esm({
|
|
|
9769
9944
|
);
|
|
9770
9945
|
this.expectedReturnStack.push(returnType);
|
|
9771
9946
|
try {
|
|
9772
|
-
const
|
|
9947
|
+
const statements = this.expressionBodyOrBlock(
|
|
9948
|
+
body,
|
|
9949
|
+
this.context.kind === "action" || this.context.kind === "setter" || this.context.kind === "constructor" || isVoid(this.context.returnType),
|
|
9950
|
+
describeUnitKind(this.context.kind)
|
|
9951
|
+
);
|
|
9952
|
+
const instructions = this.resolveStatements(statements, scope);
|
|
9773
9953
|
if ((this.context.kind === "function" || this.context.kind === "getter" || this.context.kind === "condition" || this.context.kind === "initializer") && !isVoid(this.context.returnType) && !instructionsTerminate(instructions)) {
|
|
9774
9954
|
throw new CompileError(
|
|
9775
9955
|
`Not every reachable ${FALLTHROUGH_LABELS[this.context.kind]} path returns a value or throws.`,
|
|
@@ -9790,6 +9970,24 @@ var init_strict_resolver = __esm({
|
|
|
9790
9970
|
this.expectedReturnStack.pop();
|
|
9791
9971
|
}
|
|
9792
9972
|
}
|
|
9973
|
+
/**
|
|
9974
|
+
* The statements to compile for a unit or lambda, re-deriving an
|
|
9975
|
+
* expression-bodied `=> expr` now that the target's void-ness is known.
|
|
9976
|
+
*
|
|
9977
|
+
* The parser cannot make this call: `=> DoThing()` is a returned value in a
|
|
9978
|
+
* `string` getter and a discarded statement in an action.
|
|
9979
|
+
*/
|
|
9980
|
+
expressionBodyOrBlock(node, voidContext, unitLabel2) {
|
|
9981
|
+
const authored = node.expressionBody;
|
|
9982
|
+
if (!authored) return node.body;
|
|
9983
|
+
if (authored.assignment && !voidContext) {
|
|
9984
|
+
throw new CompileError(
|
|
9985
|
+
`An expression-bodied ${unitLabel2} has to produce a value, and an assignment does not; use a block body instead.`,
|
|
9986
|
+
authored.start
|
|
9987
|
+
);
|
|
9988
|
+
}
|
|
9989
|
+
return expressionBodyStatements(authored, voidContext);
|
|
9990
|
+
}
|
|
9793
9991
|
defineValueAliases(scope) {
|
|
9794
9992
|
const aliases = this.context.valueAliases ?? [];
|
|
9795
9993
|
if (aliases.length === 0) return;
|
|
@@ -10424,7 +10622,7 @@ var init_strict_resolver = __esm({
|
|
|
10424
10622
|
};
|
|
10425
10623
|
}
|
|
10426
10624
|
throw new CompileError(
|
|
10427
|
-
"Bare expression statements aren't supported \u2014 every expression must contribute to a return / throw / variable initialiser.",
|
|
10625
|
+
statement.expressionBody ? "A void expression body has to call something; an expression whose value is discarded must move into a block body." : "Bare expression statements aren't supported \u2014 every expression must contribute to a return / throw / variable initialiser.",
|
|
10428
10626
|
statement.pos
|
|
10429
10627
|
);
|
|
10430
10628
|
}
|
|
@@ -11427,8 +11625,11 @@ var init_strict_resolver = __esm({
|
|
|
11427
11625
|
this.lambdaDepth++;
|
|
11428
11626
|
this.expectedReturnStack.push(expected.returnType);
|
|
11429
11627
|
try {
|
|
11430
|
-
const instructions = this.resolveStatements(ast.body, scope);
|
|
11431
11628
|
const returnsVoid = isVoid(expected.returnType);
|
|
11629
|
+
const instructions = this.resolveStatements(
|
|
11630
|
+
this.expressionBodyOrBlock(ast, returnsVoid, "delegate"),
|
|
11631
|
+
scope
|
|
11632
|
+
);
|
|
11432
11633
|
if (!returnsVoid && !instructionsTerminate(instructions)) {
|
|
11433
11634
|
throw new CompileError(
|
|
11434
11635
|
"Not every reachable delegate path returns a value or throws.",
|
|
@@ -11486,6 +11687,13 @@ var init_strict_resolver = __esm({
|
|
|
11486
11687
|
(token) => token.pos.line === ast.pos.line && token.pos.column === ast.pos.column
|
|
11487
11688
|
);
|
|
11488
11689
|
if (startIndex < 0) return void 0;
|
|
11690
|
+
const expressionBody = ast.expressionBody;
|
|
11691
|
+
if (expressionBody) {
|
|
11692
|
+
const start = sourceOffset(this.source, ast.pos);
|
|
11693
|
+
const end = sourceOffset(this.source, expressionBody.end);
|
|
11694
|
+
if (end <= start) return void 0;
|
|
11695
|
+
return this.source.slice(start, end).trimEnd();
|
|
11696
|
+
}
|
|
11489
11697
|
const openIndex = tokens.findIndex(
|
|
11490
11698
|
(token, index) => index > startIndex && token.kind === "punct" && token.text === "{"
|
|
11491
11699
|
);
|
|
@@ -14497,7 +14705,10 @@ var init_strict_resolver = __esm({
|
|
|
14497
14705
|
this.lambdaDepth++;
|
|
14498
14706
|
this.expectedReturnStack.push(returnType);
|
|
14499
14707
|
try {
|
|
14500
|
-
const instructions = this.resolveStatements(
|
|
14708
|
+
const instructions = this.resolveStatements(
|
|
14709
|
+
this.expressionBodyOrBlock(ast, false, "lambda"),
|
|
14710
|
+
scope
|
|
14711
|
+
);
|
|
14501
14712
|
if (!instructionsTerminate(instructions)) {
|
|
14502
14713
|
throw new CompileError(
|
|
14503
14714
|
"Not every reachable lambda path returns a value or throws.",
|
|
@@ -15490,14 +15701,15 @@ function compileNeoScriptStrict(document, context, options = {}) {
|
|
|
15490
15701
|
(unit) => unit.kind === "function" || unit.kind === "body"
|
|
15491
15702
|
);
|
|
15492
15703
|
const expressionDocument = context.kind === "initializerExpression";
|
|
15493
|
-
const
|
|
15704
|
+
const expressionBodyUnit2 = options.bodyMode === "expression";
|
|
15705
|
+
const compilableSource = expressionBodyUnit2 ? document.text : expressionDocument ? `return
|
|
15494
15706
|
${document.text};` : functionUnit ? isolateUnitBody(
|
|
15495
15707
|
document.text,
|
|
15496
15708
|
functionUnit.bodyStart,
|
|
15497
15709
|
functionUnit.bodyEnd
|
|
15498
15710
|
) : document.text;
|
|
15499
15711
|
const resolverContext = expressionDocument ? { ...context, kind: "initializer" } : context;
|
|
15500
|
-
const ast = parseFunctionBody(compilableSource);
|
|
15712
|
+
const ast = expressionBodyUnit2 ? parseExpressionBody(compilableSource) : parseFunctionBody(compilableSource);
|
|
15501
15713
|
const projectIndex = options.projectIndex ?? createProjectIndex(resolverContext.project);
|
|
15502
15714
|
const emitted = new StrictNeoScriptResolver(
|
|
15503
15715
|
resolverContext,
|
|
@@ -15556,7 +15768,7 @@ function compileProperty(document, context, units, projectIndex) {
|
|
|
15556
15768
|
const getterUnit = units.find((unit) => unit.kind === "getter");
|
|
15557
15769
|
if (!getterUnit) {
|
|
15558
15770
|
throw new CompileError(
|
|
15559
|
-
"A NeoScript property sidecar must declare one `get { ... }` unit.",
|
|
15771
|
+
"A NeoScript property sidecar must declare one `get { ... }` or `get => ...;` unit.",
|
|
15560
15772
|
{ line: 1, column: 1 },
|
|
15561
15773
|
"getter"
|
|
15562
15774
|
);
|
|
@@ -15584,7 +15796,7 @@ function compileProperty(document, context, units, projectIndex) {
|
|
|
15584
15796
|
function compileUnit(source, unit, context, label, projectIndex) {
|
|
15585
15797
|
const isolated = isolateUnitBody(source, unit.bodyStart, unit.bodyEnd);
|
|
15586
15798
|
try {
|
|
15587
|
-
const ast = parseFunctionBody(isolated);
|
|
15799
|
+
const ast = unit.expressionBody ? parseExpressionBody(isolated) : parseFunctionBody(isolated);
|
|
15588
15800
|
const indexedProject = projectIndex ?? createProjectIndex(context.project);
|
|
15589
15801
|
return attachDependencyManifest(
|
|
15590
15802
|
new StrictNeoScriptResolver(context, indexedProject, isolated).compile(
|
|
@@ -17950,8 +18162,8 @@ var init_project_source_parser = __esm({
|
|
|
17950
18162
|
docsText
|
|
17951
18163
|
);
|
|
17952
18164
|
}
|
|
17953
|
-
if (this.at("{")) {
|
|
17954
|
-
const body = this.captureBody();
|
|
18165
|
+
if (this.at("{") || this.at("=>")) {
|
|
18166
|
+
const body = this.at("{") ? this.captureBody() : this.captureExpressionBody("property");
|
|
17955
18167
|
this.reportNestedTypes(body);
|
|
17956
18168
|
const property2 = {
|
|
17957
18169
|
kind: "property",
|
|
@@ -18002,7 +18214,7 @@ var init_project_source_parser = __esm({
|
|
|
18002
18214
|
if (this.eat(";")) {
|
|
18003
18215
|
body = void 0;
|
|
18004
18216
|
} else {
|
|
18005
|
-
body = this.captureBody();
|
|
18217
|
+
body = this.at("=>") ? this.captureExpressionBody("method") : this.captureBody();
|
|
18006
18218
|
this.reportNestedTypes(body);
|
|
18007
18219
|
}
|
|
18008
18220
|
return {
|
|
@@ -18340,6 +18552,36 @@ var init_project_source_parser = __esm({
|
|
|
18340
18552
|
range: rangeFrom(open, close)
|
|
18341
18553
|
};
|
|
18342
18554
|
}
|
|
18555
|
+
/**
|
|
18556
|
+
* P72 §2. `=> expr;` in member position, reached only when the arrow follows
|
|
18557
|
+
* the member name or the parameter list's `)`. An arrow that follows an `=`
|
|
18558
|
+
* initializer never gets here, so a NeoFlow destination keeps its meaning.
|
|
18559
|
+
*/
|
|
18560
|
+
captureExpressionBody(label) {
|
|
18561
|
+
const arrow = this.expect("=>");
|
|
18562
|
+
const expression = this.captureUntilTopLevel(/* @__PURE__ */ new Set([";"]), true);
|
|
18563
|
+
if (expression.text.trim().length === 0) {
|
|
18564
|
+
throw this.failure(
|
|
18565
|
+
"empty-expression-body",
|
|
18566
|
+
`An expression-bodied ${label} needs an expression after \`=>\`.`,
|
|
18567
|
+
this.peek()
|
|
18568
|
+
);
|
|
18569
|
+
}
|
|
18570
|
+
const close = this.previous();
|
|
18571
|
+
if (!this.at(";")) {
|
|
18572
|
+
throw this.failure(
|
|
18573
|
+
"unterminated-expression-body",
|
|
18574
|
+
`An expression-bodied ${label} must end with \`;\`.`,
|
|
18575
|
+
this.peek()
|
|
18576
|
+
);
|
|
18577
|
+
}
|
|
18578
|
+
this.next();
|
|
18579
|
+
return {
|
|
18580
|
+
text: this.source.text.slice(arrow.start, close.end),
|
|
18581
|
+
range: rangeFrom(arrow, close),
|
|
18582
|
+
expression
|
|
18583
|
+
};
|
|
18584
|
+
}
|
|
18343
18585
|
captureUntilTopLevel(terminators, allowImplicitBodyEnd) {
|
|
18344
18586
|
const start = this.peek();
|
|
18345
18587
|
const startCursor = this.cursor;
|
|
@@ -24771,6 +25013,10 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
|
|
|
24771
25013
|
const body = source.slice(bodyStart, bodyEnd);
|
|
24772
25014
|
const local = new SourceText(body);
|
|
24773
25015
|
const tokens = lex2(body);
|
|
25016
|
+
if (tokens[0]?.text === "=>") {
|
|
25017
|
+
if (keyword !== "get") return null;
|
|
25018
|
+
return expressionAccessor(source, full, local, tokens, bodyStart, 1);
|
|
25019
|
+
}
|
|
24774
25020
|
let depth = 0;
|
|
24775
25021
|
for (let index = 0; index < tokens.length; index++) {
|
|
24776
25022
|
const token = tokens[index];
|
|
@@ -24797,6 +25043,16 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
|
|
|
24797
25043
|
if (parameterDepth !== 0) return null;
|
|
24798
25044
|
}
|
|
24799
25045
|
const open = tokens[openIndex];
|
|
25046
|
+
if (open?.text === "=>") {
|
|
25047
|
+
return expressionAccessor(
|
|
25048
|
+
source,
|
|
25049
|
+
full,
|
|
25050
|
+
local,
|
|
25051
|
+
tokens,
|
|
25052
|
+
bodyStart,
|
|
25053
|
+
openIndex + 1
|
|
25054
|
+
);
|
|
25055
|
+
}
|
|
24800
25056
|
if (open?.text !== "{") return null;
|
|
24801
25057
|
let accessorDepth = 1;
|
|
24802
25058
|
for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
|
|
@@ -24808,13 +25064,52 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
|
|
|
24808
25064
|
const end = bodyStart + sourceOffset2(local, candidate.pos);
|
|
24809
25065
|
return {
|
|
24810
25066
|
range: full.range(start, end),
|
|
24811
|
-
source: source.slice(start, end)
|
|
25067
|
+
source: source.slice(start, end),
|
|
25068
|
+
form: "block"
|
|
24812
25069
|
};
|
|
24813
25070
|
}
|
|
24814
25071
|
return null;
|
|
24815
25072
|
}
|
|
24816
25073
|
return null;
|
|
24817
25074
|
}
|
|
25075
|
+
function expressionAccessor(source, full, local, tokens, bodyStart, firstIndex) {
|
|
25076
|
+
const first = tokens[firstIndex];
|
|
25077
|
+
if (!first) return null;
|
|
25078
|
+
const start = bodyStart + sourceOffset2(local, first.pos);
|
|
25079
|
+
let end = bodyStart + local.text.length;
|
|
25080
|
+
let depth = 0;
|
|
25081
|
+
for (let cursor = firstIndex; cursor < tokens.length; cursor++) {
|
|
25082
|
+
const token = tokens[cursor];
|
|
25083
|
+
if (token.kind !== "punct") continue;
|
|
25084
|
+
if (token.text === "{" || token.text === "(" || token.text === "[") {
|
|
25085
|
+
depth++;
|
|
25086
|
+
continue;
|
|
25087
|
+
}
|
|
25088
|
+
if (token.text === ")" || token.text === "]") {
|
|
25089
|
+
depth--;
|
|
25090
|
+
continue;
|
|
25091
|
+
}
|
|
25092
|
+
if (token.text === "}") {
|
|
25093
|
+
if (depth === 0) {
|
|
25094
|
+
end = bodyStart + sourceOffset2(local, token.pos);
|
|
25095
|
+
break;
|
|
25096
|
+
}
|
|
25097
|
+
depth--;
|
|
25098
|
+
continue;
|
|
25099
|
+
}
|
|
25100
|
+
if (depth === 0 && token.text === ";") {
|
|
25101
|
+
end = bodyStart + sourceOffset2(local, token.pos);
|
|
25102
|
+
break;
|
|
25103
|
+
}
|
|
25104
|
+
}
|
|
25105
|
+
const trimmed = source.slice(start, end).trimEnd();
|
|
25106
|
+
if (trimmed.length === 0) return null;
|
|
25107
|
+
return {
|
|
25108
|
+
range: full.range(start, start + trimmed.length),
|
|
25109
|
+
source: trimmed,
|
|
25110
|
+
form: "expression"
|
|
25111
|
+
};
|
|
25112
|
+
}
|
|
24818
25113
|
function sourceOffset2(source, position) {
|
|
24819
25114
|
return source.offsetAt({
|
|
24820
25115
|
line: Math.max(0, position.line - 1),
|
|
@@ -24939,6 +25234,7 @@ function compileProjectSourceBodies(documents) {
|
|
|
24939
25234
|
staticMember
|
|
24940
25235
|
};
|
|
24941
25236
|
if (member.kind === "function" && member.body) {
|
|
25237
|
+
const expressionBody = member.body.expression;
|
|
24942
25238
|
compileBody({
|
|
24943
25239
|
uri,
|
|
24944
25240
|
document,
|
|
@@ -24947,7 +25243,8 @@ function compileProjectSourceBodies(documents) {
|
|
|
24947
25243
|
declarationRange: member.nameRange,
|
|
24948
25244
|
memberId: info.symbol.id,
|
|
24949
25245
|
unit: "function",
|
|
24950
|
-
range: innerBodyRange(document.sourceText, member.body.range),
|
|
25246
|
+
range: expressionBody?.range ?? innerBodyRange(document.sourceText, member.body.range),
|
|
25247
|
+
...expressionBody ? { bodyMode: "expression" } : {},
|
|
24951
25248
|
context: {
|
|
24952
25249
|
...baseContext2,
|
|
24953
25250
|
kind: "function",
|
|
@@ -24970,12 +25267,12 @@ function compileProjectSourceBodies(documents) {
|
|
|
24970
25267
|
}
|
|
24971
25268
|
if (member.kind !== "property") continue;
|
|
24972
25269
|
for (const unit of ["getter", "setter"]) {
|
|
24973
|
-
const
|
|
25270
|
+
const accessor = findProjectSourceAccessorBody(
|
|
24974
25271
|
document.sourceText,
|
|
24975
25272
|
unit === "getter" ? "get" : "set",
|
|
24976
25273
|
member.body.range
|
|
24977
|
-
)
|
|
24978
|
-
if (!
|
|
25274
|
+
);
|
|
25275
|
+
if (!accessor) continue;
|
|
24979
25276
|
compileBody({
|
|
24980
25277
|
uri,
|
|
24981
25278
|
document,
|
|
@@ -24984,7 +25281,8 @@ function compileProjectSourceBodies(documents) {
|
|
|
24984
25281
|
declarationRange: member.nameRange,
|
|
24985
25282
|
memberId: info.symbol.id,
|
|
24986
25283
|
unit,
|
|
24987
|
-
range:
|
|
25284
|
+
range: accessor.range,
|
|
25285
|
+
...accessor.form === "expression" ? { bodyMode: "expression" } : {},
|
|
24988
25286
|
context: { ...baseContext2, kind: unit },
|
|
24989
25287
|
projectIndex,
|
|
24990
25288
|
bodies,
|
|
@@ -25017,7 +25315,10 @@ function compileBody(args) {
|
|
|
25017
25315
|
text: maskOutside(args.document.sourceText, start, end)
|
|
25018
25316
|
},
|
|
25019
25317
|
args.context,
|
|
25020
|
-
{
|
|
25318
|
+
{
|
|
25319
|
+
projectIndex: args.projectIndex,
|
|
25320
|
+
...args.bodyMode ? { bodyMode: args.bodyMode } : {}
|
|
25321
|
+
}
|
|
25021
25322
|
);
|
|
25022
25323
|
for (const diagnostic of result.diagnostics) {
|
|
25023
25324
|
args.diagnostics.push({
|
|
@@ -26614,7 +26915,7 @@ function validateClassMemberModifiers(ref, environment, diagnostics) {
|
|
|
26614
26915
|
`Abstract member '${member.name}' cannot declare a default initializer.`
|
|
26615
26916
|
);
|
|
26616
26917
|
}
|
|
26617
|
-
if (modifiers.has("abstract") && member.kind === "property" && /\b(?:get|set)\s*\{/.test(member.body.text)) {
|
|
26918
|
+
if (modifiers.has("abstract") && member.kind === "property" && (member.body.expression !== void 0 || /\b(?:get|set)\s*\{/.test(member.body.text))) {
|
|
26618
26919
|
diagnose(
|
|
26619
26920
|
diagnostics,
|
|
26620
26921
|
ref.uri,
|
|
@@ -27017,6 +27318,7 @@ function valueAccessors(member, environment) {
|
|
|
27017
27318
|
};
|
|
27018
27319
|
}
|
|
27019
27320
|
if (member.kind === "function") return { get: false, set: false };
|
|
27321
|
+
if (member.body.expression) return { get: true, set: false };
|
|
27020
27322
|
return {
|
|
27021
27323
|
get: /\bget\b/.test(member.body.text),
|
|
27022
27324
|
set: /\bset\b/.test(member.body.text)
|
|
@@ -33296,9 +33598,10 @@ function scriptDefinitionLocations(member, environment, field) {
|
|
|
33296
33598
|
const firstNonWhitespace = sourceText.search(/\S/);
|
|
33297
33599
|
const startOffset = headerMember?.[1] === void 0 ? Math.max(firstNonWhitespace, 0) : sourceText.indexOf(headerMember[1]);
|
|
33298
33600
|
const start = positionAt(sourceText, startOffset);
|
|
33299
|
-
const tokenLength =
|
|
33300
|
-
|
|
33301
|
-
|
|
33601
|
+
const tokenLength = definitionTokenLength(
|
|
33602
|
+
sourceText.slice(startOffset),
|
|
33603
|
+
kind,
|
|
33604
|
+
headerMember?.[1]
|
|
33302
33605
|
);
|
|
33303
33606
|
return {
|
|
33304
33607
|
definitionLocations: [
|
|
@@ -33312,6 +33615,12 @@ function scriptDefinitionLocations(member, environment, field) {
|
|
|
33312
33615
|
]
|
|
33313
33616
|
};
|
|
33314
33617
|
}
|
|
33618
|
+
function definitionTokenLength(tail, kind, headerName) {
|
|
33619
|
+
if (headerName !== void 0) return headerName.length;
|
|
33620
|
+
if (kind === "property" && tail.startsWith("get")) return 3;
|
|
33621
|
+
if (tail.startsWith("=>")) return 2;
|
|
33622
|
+
return Math.max(/^[A-Za-z_][A-Za-z0-9_]*/.exec(tail)?.[0].length ?? 1, 1);
|
|
33623
|
+
}
|
|
33315
33624
|
function positionAt(text, offset) {
|
|
33316
33625
|
let line = 0;
|
|
33317
33626
|
let character = 0;
|
|
@@ -50964,6 +51273,7 @@ function emitProjectSourcesV4(manifest, options = {}) {
|
|
|
50964
51273
|
fileSymbols: options.fileSymbols ?? /* @__PURE__ */ new Map(),
|
|
50965
51274
|
collectionValuePaths: options.collectionValuePaths ?? /* @__PURE__ */ new Map(),
|
|
50966
51275
|
variantPathsById: buildVariantPathsByIdV4(manifest),
|
|
51276
|
+
formAlternates: [],
|
|
50967
51277
|
templateNames: new Map(
|
|
50968
51278
|
[...manifest.textureTemplates, ...manifest.audioTemplates].map(
|
|
50969
51279
|
(template) => [template.id, template.name]
|
|
@@ -50971,6 +51281,7 @@ function emitProjectSourcesV4(manifest, options = {}) {
|
|
|
50971
51281
|
)
|
|
50972
51282
|
};
|
|
50973
51283
|
const files = [];
|
|
51284
|
+
const formAlternates = [];
|
|
50974
51285
|
const localizationStatusNames = new Map(
|
|
50975
51286
|
manifest.localizationStatuses.map((entry) => [
|
|
50976
51287
|
entry.id,
|
|
@@ -50982,9 +51293,14 @@ function emitProjectSourcesV4(manifest, options = {}) {
|
|
|
50982
51293
|
const relationIds = manifest.internalRecordRelations.filter(
|
|
50983
51294
|
(relation) => relation.sourceRecordKind === "class" && relation.sourceRecordId === schemaClass2.id && specializedRelationProperty(relation.relationKind) !== null
|
|
50984
51295
|
).map((relation) => relation.id);
|
|
51296
|
+
const path = `Classes/${schemaClass2.name}.neo`;
|
|
51297
|
+
const content = emitClass(context, schemaClass2, ownMemberIds);
|
|
51298
|
+
formAlternates.push(
|
|
51299
|
+
...context.formAlternates.splice(0).map((draft) => ({ path, ...draft }))
|
|
51300
|
+
);
|
|
50985
51301
|
files.push({
|
|
50986
|
-
path
|
|
50987
|
-
content
|
|
51302
|
+
path,
|
|
51303
|
+
content,
|
|
50988
51304
|
recordKeys: [
|
|
50989
51305
|
`class:${schemaClass2.id}`,
|
|
50990
51306
|
...(schemaClass2.constructorIds ?? []).map((id2) => `constructor:${id2}`),
|
|
@@ -51067,7 +51383,7 @@ function emitProjectSourcesV4(manifest, options = {}) {
|
|
|
51067
51383
|
for (const file of files) {
|
|
51068
51384
|
for (const key of file.recordKeys) recordFiles.set(key, file.path);
|
|
51069
51385
|
}
|
|
51070
|
-
return { files, recordFiles };
|
|
51386
|
+
return { files, recordFiles, formAlternates };
|
|
51071
51387
|
}
|
|
51072
51388
|
function emitGenericRelations(context, relations, endpointExpressions) {
|
|
51073
51389
|
const ordered = [...relations].sort(
|
|
@@ -51197,10 +51513,16 @@ ${schemaClass2.genericParameters.map((parameter4) => {
|
|
|
51197
51513
|
const header = `${modifier}class ${schemaClass2.name}${generics}${headerParameters}${bases.length ? ` : ${bases.join(", ")}` : ""} {`;
|
|
51198
51514
|
const rendered = memberIds.map((memberId) => {
|
|
51199
51515
|
const member = required(context.members, memberId, "member");
|
|
51200
|
-
|
|
51201
|
-
|
|
51202
|
-
|
|
51203
|
-
|
|
51516
|
+
const alternateIndex = context.formAlternates.length;
|
|
51517
|
+
const source = indentNeoSourceNonEmptyLines(emitMember(context, member, schemaClass2.id), 2);
|
|
51518
|
+
for (let at = alternateIndex; at < context.formAlternates.length; at++) {
|
|
51519
|
+
const alternate = context.formAlternates[at];
|
|
51520
|
+
context.formAlternates[at] = {
|
|
51521
|
+
shorthand: indentNeoSourceNonEmptyLines(alternate.shorthand, 2),
|
|
51522
|
+
block: indentNeoSourceNonEmptyLines(alternate.block, 2)
|
|
51523
|
+
};
|
|
51524
|
+
}
|
|
51525
|
+
return { member, source };
|
|
51204
51526
|
});
|
|
51205
51527
|
const constructorIds = schemaClass2.constructorIds ?? [];
|
|
51206
51528
|
const sources = rendered.map((entry) => entry.source);
|
|
@@ -51431,25 +51753,44 @@ function emitMember(context, member, enclosingClassId) {
|
|
|
51431
51753
|
if (member.kind === "computed") {
|
|
51432
51754
|
const getter = member.script?.getterSource?.trim();
|
|
51433
51755
|
const setter = member.script?.setterSource?.trim();
|
|
51434
|
-
const
|
|
51435
|
-
|
|
51436
|
-
|
|
51437
|
-
|
|
51756
|
+
const head = `${annotations.join("\n")}
|
|
51757
|
+
${prefix}${renderType(context, member.returnType)} ${member.name}`;
|
|
51758
|
+
if (!getter) return `${head} { get; }`;
|
|
51759
|
+
const setterBlock = setter ? `
|
|
51438
51760
|
|
|
51439
51761
|
set {
|
|
51440
51762
|
${indentNeoSourceNonEmptyLines(setter, 4)}
|
|
51441
|
-
}` : ""
|
|
51442
|
-
|
|
51443
|
-
|
|
51444
|
-
${
|
|
51763
|
+
}` : "";
|
|
51764
|
+
const block = `${head} {
|
|
51765
|
+
get {
|
|
51766
|
+
${indentNeoSourceNonEmptyLines(getter, 4)}
|
|
51767
|
+
}${setterBlock}
|
|
51768
|
+
}`;
|
|
51769
|
+
const expression = expressionBodyExpression(getter);
|
|
51770
|
+
if (expression === null) return block;
|
|
51771
|
+
const shorthand = setter ? `${head} {
|
|
51772
|
+
get => ${expression};${setterBlock}
|
|
51773
|
+
}` : `${head} => ${expression};`;
|
|
51774
|
+
context.formAlternates.push({ shorthand, block });
|
|
51775
|
+
return shorthand;
|
|
51445
51776
|
}
|
|
51446
51777
|
if (member.kind === "function" || member.kind === "scriptFunction") {
|
|
51447
51778
|
const signature = `${prefix}${member.deferred ? "async " : ""}${renderReturnType(context, member.returnType)} ${member.name}(${member.arguments.map((argument2) => renderParameter(context, argument2)).join(", ")})`;
|
|
51448
51779
|
const tracked = member.kind === "scriptFunction" ? member.script?.sourceText ?? uiFunctionSource(context, member) : null;
|
|
51449
51780
|
const body = tracked === null ? null : emitFunctionBody(tracked);
|
|
51450
51781
|
const abstractContract = member.modifier === "abstract" || member.modifier === "abstractOverride";
|
|
51451
|
-
|
|
51452
|
-
|
|
51782
|
+
if (body === null) {
|
|
51783
|
+
return `${annotations.join("\n")}
|
|
51784
|
+
${abstractContract ? `${signature};` : `native ${signature};`}`;
|
|
51785
|
+
}
|
|
51786
|
+
const block = `${annotations.join("\n")}
|
|
51787
|
+
${signature} ${body}`;
|
|
51788
|
+
const expression = expressionBodyExpression(tracked ?? "");
|
|
51789
|
+
if (expression === null) return block;
|
|
51790
|
+
const shorthand = `${annotations.join("\n")}
|
|
51791
|
+
${signature} => ${expression};`;
|
|
51792
|
+
context.formAlternates.push({ shorthand, block });
|
|
51793
|
+
return shorthand;
|
|
51453
51794
|
}
|
|
51454
51795
|
const type = renderMemberType(context, member, enclosingClassId);
|
|
51455
51796
|
const initializer = initDefaultSource(member) ?? (member.isStatic ? context.staticInitializers.get(member.id) : void 0) ?? context.defaultInitializers.get(member.id) ?? renderDefault(context, member);
|
|
@@ -52320,6 +52661,22 @@ function systemAnnotation2(system) {
|
|
|
52320
52661
|
];
|
|
52321
52662
|
return `@system(${args.join(", ")})`;
|
|
52322
52663
|
}
|
|
52664
|
+
function expressionBodyExpression(sourceText) {
|
|
52665
|
+
const trimmed = sourceText.trim();
|
|
52666
|
+
if (trimmed.includes("\n")) return null;
|
|
52667
|
+
if (!trimmed.startsWith("return") || !trimmed.endsWith(";")) return null;
|
|
52668
|
+
let parsed;
|
|
52669
|
+
try {
|
|
52670
|
+
parsed = parseFunctionBody(trimmed);
|
|
52671
|
+
} catch {
|
|
52672
|
+
return null;
|
|
52673
|
+
}
|
|
52674
|
+
const statement = parsed.body[0];
|
|
52675
|
+
if (parsed.body.length !== 1 || statement?.kind !== "return") return null;
|
|
52676
|
+
if (statement.expr === null) return null;
|
|
52677
|
+
const expression = trimmed.slice("return".length, -1).trim();
|
|
52678
|
+
return expression.length === 0 ? null : expression;
|
|
52679
|
+
}
|
|
52323
52680
|
function emitFunctionBody(sourceText) {
|
|
52324
52681
|
const code = sourceText.trim();
|
|
52325
52682
|
if (code.length === 0) return "{\n}";
|
|
@@ -53772,7 +54129,7 @@ function parseDialogueGroupFunctions(source) {
|
|
|
53772
54129
|
return {
|
|
53773
54130
|
id: annotationId(member.annotations),
|
|
53774
54131
|
name: member.name,
|
|
53775
|
-
code: member.body === void 0 ? null :
|
|
54132
|
+
code: member.body === void 0 ? null : dialogueGroupCode(member.body)
|
|
53776
54133
|
};
|
|
53777
54134
|
});
|
|
53778
54135
|
}
|
|
@@ -53825,8 +54182,9 @@ function annotationId(annotations) {
|
|
|
53825
54182
|
}
|
|
53826
54183
|
return parsed;
|
|
53827
54184
|
}
|
|
53828
|
-
function
|
|
53829
|
-
|
|
54185
|
+
function dialogueGroupCode(body) {
|
|
54186
|
+
if (body.expression) return `return ${body.expression.text.trim()};`;
|
|
54187
|
+
const trimmed = body.text.trim();
|
|
53830
54188
|
return trimmed.slice(1, -1).trim();
|
|
53831
54189
|
}
|
|
53832
54190
|
function resolveOptionalReference(expression, symbols) {
|
|
@@ -57817,7 +58175,7 @@ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
|
|
|
57817
58175
|
}
|
|
57818
58176
|
if (declaration.body !== null) {
|
|
57819
58177
|
const sourceText = preserveEquivalentCode(
|
|
57820
|
-
|
|
58178
|
+
functionBodySource(declaration.body, returnType.kind === "void"),
|
|
57821
58179
|
base?.kind === "scriptFunction" ? base.script?.sourceText : null
|
|
57822
58180
|
);
|
|
57823
58181
|
const uiAction = logicMode === "UI" ? lowerUiFunctionAction(
|
|
@@ -59119,7 +59477,7 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
|
|
|
59119
59477
|
`A lambda at ${path} requires a NeoDelegate expected type.`
|
|
59120
59478
|
);
|
|
59121
59479
|
}
|
|
59122
|
-
const code = sourceText === void 0 ? null : lambdaExpressionSource(sourceText, expression
|
|
59480
|
+
const code = sourceText === void 0 ? null : lambdaExpressionSource(sourceText, expression);
|
|
59123
59481
|
if (code === null || code.length === 0) {
|
|
59124
59482
|
throw new Error(`Delegate value ${path} has no recoverable source.`);
|
|
59125
59483
|
}
|
|
@@ -59232,14 +59590,25 @@ function isStoredActionLiteral(type, expression) {
|
|
|
59232
59590
|
while (current.kind === "annotated") current = current.expression;
|
|
59233
59591
|
return current.kind === "litList";
|
|
59234
59592
|
}
|
|
59235
|
-
function
|
|
59236
|
-
let
|
|
59593
|
+
function sourceOffsetAt(source, pos) {
|
|
59594
|
+
let offset = 0;
|
|
59237
59595
|
for (let line = 1; line < pos.line; line += 1) {
|
|
59238
|
-
const next = source.indexOf("\n",
|
|
59596
|
+
const next = source.indexOf("\n", offset);
|
|
59239
59597
|
if (next < 0) return null;
|
|
59240
|
-
|
|
59598
|
+
offset = next + 1;
|
|
59599
|
+
}
|
|
59600
|
+
return Math.min(offset + pos.column - 1, source.length);
|
|
59601
|
+
}
|
|
59602
|
+
function lambdaExpressionSource(source, lambda) {
|
|
59603
|
+
const start = sourceOffsetAt(source, lambda.pos);
|
|
59604
|
+
if (start === null) return null;
|
|
59605
|
+
const expressionBody = lambda.expressionBody;
|
|
59606
|
+
if (expressionBody) {
|
|
59607
|
+
const end = sourceOffsetAt(source, expressionBody.end);
|
|
59608
|
+
if (end === null || end <= start) return null;
|
|
59609
|
+
const slice = source.slice(start, end).trimEnd();
|
|
59610
|
+
return slice.length === 0 ? null : slice;
|
|
59241
59611
|
}
|
|
59242
|
-
start = Math.min(start + pos.column - 1, source.length);
|
|
59243
59612
|
const arrow = source.indexOf("=>", start);
|
|
59244
59613
|
if (arrow < 0) return null;
|
|
59245
59614
|
const bodyStart = source.indexOf("{", arrow + 2);
|
|
@@ -59837,14 +60206,22 @@ function memberModifier3(modifiers, baseModifier) {
|
|
|
59837
60206
|
function propertyAccessors(body) {
|
|
59838
60207
|
const accessorBody = (keyword) => {
|
|
59839
60208
|
const accessor = findProjectSourceAccessorBody(body, keyword);
|
|
59840
|
-
|
|
60209
|
+
if (accessor === null) return null;
|
|
60210
|
+
const source = normalizeBodySource(accessor.source);
|
|
60211
|
+
if (accessor.form === "block") return source;
|
|
60212
|
+
return keyword === "get" ? `return ${source};` : `${source};`;
|
|
59841
60213
|
};
|
|
59842
60214
|
return {
|
|
59843
60215
|
get: accessorBody("get"),
|
|
59844
60216
|
set: accessorBody("set")
|
|
59845
60217
|
};
|
|
59846
60218
|
}
|
|
59847
|
-
function
|
|
60219
|
+
function functionBodySource(body, isVoid2) {
|
|
60220
|
+
const trimmed = body.trim();
|
|
60221
|
+
if (trimmed.startsWith("=>")) {
|
|
60222
|
+
const expression = normalizeBodySource(trimmed.slice(2));
|
|
60223
|
+
return isVoid2 ? `${expression};` : `return ${expression};`;
|
|
60224
|
+
}
|
|
59848
60225
|
const start = body.indexOf("{");
|
|
59849
60226
|
const end = body.lastIndexOf("}");
|
|
59850
60227
|
if (start < 0 || end <= start) return normalizeBodySource(body);
|
|
@@ -103466,7 +103843,13 @@ ${errors.map(
|
|
|
103466
103843
|
).join("\n")}`
|
|
103467
103844
|
);
|
|
103468
103845
|
}
|
|
103469
|
-
return {
|
|
103846
|
+
return {
|
|
103847
|
+
files,
|
|
103848
|
+
recordFiles,
|
|
103849
|
+
analysis,
|
|
103850
|
+
materializedConstructors,
|
|
103851
|
+
formAlternates: source.formAlternates
|
|
103852
|
+
};
|
|
103470
103853
|
}
|
|
103471
103854
|
function relationEndpointExpressions(records2) {
|
|
103472
103855
|
const ownerByMemberId = /* @__PURE__ */ new Map();
|
|
@@ -112325,6 +112708,30 @@ function applyPushResult(workspace, result, pendingIdAssignments = /* @__PURE__
|
|
|
112325
112708
|
rewriteFilesFromState(workspace, void 0, preservedSourceFiles);
|
|
112326
112709
|
writeWorkspaceState(workspace.root, workspace.state);
|
|
112327
112710
|
}
|
|
112711
|
+
function authoredMemberFormsByFile(root, alternates) {
|
|
112712
|
+
const authored = /* @__PURE__ */ new Map();
|
|
112713
|
+
const contents = /* @__PURE__ */ new Map();
|
|
112714
|
+
for (const alternate of alternates) {
|
|
112715
|
+
let existing = contents.get(alternate.path);
|
|
112716
|
+
if (existing === void 0) {
|
|
112717
|
+
const absolute = join15(root, alternate.path);
|
|
112718
|
+
existing = existsSync11(absolute) ? readFileSync15(absolute, "utf8") : null;
|
|
112719
|
+
contents.set(alternate.path, existing);
|
|
112720
|
+
}
|
|
112721
|
+
if (existing === null || !existing.includes(alternate.block)) continue;
|
|
112722
|
+
const bucket = authored.get(alternate.path);
|
|
112723
|
+
if (bucket) bucket.push(alternate);
|
|
112724
|
+
else authored.set(alternate.path, [alternate]);
|
|
112725
|
+
}
|
|
112726
|
+
return authored;
|
|
112727
|
+
}
|
|
112728
|
+
function restoreAuthoredMemberForms(content, alternates) {
|
|
112729
|
+
let result = content;
|
|
112730
|
+
for (const alternate of alternates) {
|
|
112731
|
+
result = result.split(alternate.shorthand).join(alternate.block);
|
|
112732
|
+
}
|
|
112733
|
+
return result;
|
|
112734
|
+
}
|
|
112328
112735
|
function rewriteFilesFromState(workspace, records2 = new Map(
|
|
112329
112736
|
Object.entries(workspace.state.records).map(([key, recordState]) => [
|
|
112330
112737
|
key,
|
|
@@ -112338,9 +112745,16 @@ function rewriteFilesFromState(workspace, records2 = new Map(
|
|
|
112338
112745
|
])
|
|
112339
112746
|
), preservedSourceFiles = /* @__PURE__ */ new Map()) {
|
|
112340
112747
|
const result = emitProjectDocumentFilesV4(records2);
|
|
112748
|
+
const authoredForms = authoredMemberFormsByFile(
|
|
112749
|
+
workspace.root,
|
|
112750
|
+
result.formAlternates
|
|
112751
|
+
);
|
|
112341
112752
|
const files = result.files.map((file) => ({
|
|
112342
112753
|
...file,
|
|
112343
|
-
content: preservedSourceFiles.get(file.path) ??
|
|
112754
|
+
content: preservedSourceFiles.get(file.path) ?? restoreAuthoredMemberForms(
|
|
112755
|
+
file.content,
|
|
112756
|
+
authoredForms.get(file.path) ?? []
|
|
112757
|
+
)
|
|
112344
112758
|
}));
|
|
112345
112759
|
const finalAnalysis = compileNeoProjectSources(
|
|
112346
112760
|
files.map((file) => {
|
|
@@ -112487,7 +112901,7 @@ function materializeAssignedSchemaIdsInAuthoredSource(workspace, replacements, p
|
|
|
112487
112901
|
if (source === void 0) continue;
|
|
112488
112902
|
const edits = editsByUri.get(uri) ?? [];
|
|
112489
112903
|
if (entry.identity.id === null) {
|
|
112490
|
-
const start =
|
|
112904
|
+
const start = sourceOffsetAt2(
|
|
112491
112905
|
source,
|
|
112492
112906
|
entry.identity.source.range.start.line,
|
|
112493
112907
|
entry.identity.source.range.start.character
|
|
@@ -112519,7 +112933,7 @@ function materializeAssignedSchemaIdsInAuthoredSource(workspace, replacements, p
|
|
|
112519
112933
|
if (assignedId === void 0) continue;
|
|
112520
112934
|
const source = inputByUri.get(site.uri);
|
|
112521
112935
|
if (source === void 0) continue;
|
|
112522
|
-
const declarationOffset =
|
|
112936
|
+
const declarationOffset = sourceOffsetAt2(
|
|
112523
112937
|
source,
|
|
112524
112938
|
site.declarationStart.line,
|
|
112525
112939
|
site.declarationStart.character
|
|
@@ -112587,7 +113001,7 @@ function pendingIdentityUri(pendingId2) {
|
|
|
112587
113001
|
return null;
|
|
112588
113002
|
}
|
|
112589
113003
|
}
|
|
112590
|
-
function
|
|
113004
|
+
function sourceOffsetAt2(source, line, character) {
|
|
112591
113005
|
let offset = 0;
|
|
112592
113006
|
for (let currentLine = 0; currentLine < line; currentLine += 1) {
|
|
112593
113007
|
const newline = source.indexOf("\n", offset);
|
|
@@ -113181,7 +113595,7 @@ var init_registry2 = __esm({
|
|
|
113181
113595
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
113182
113596
|
formatVersion: 3,
|
|
113183
113597
|
contractVersion: "3.14",
|
|
113184
|
-
cliVersion: "0.
|
|
113598
|
+
cliVersion: "0.35.0",
|
|
113185
113599
|
projectFileUploadBatchSize: 32,
|
|
113186
113600
|
documentRecords: {
|
|
113187
113601
|
member: {
|
|
@@ -119783,7 +120197,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
119783
120197
|
async function main() {
|
|
119784
120198
|
const args = parseArgs(process.argv.slice(2));
|
|
119785
120199
|
if (args.command === "--version") {
|
|
119786
|
-
console.log("0.
|
|
120200
|
+
console.log("0.35.0");
|
|
119787
120201
|
return;
|
|
119788
120202
|
}
|
|
119789
120203
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
`@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<!-- reviewed-through-cli: 0.
|
|
12
|
+
<!-- reviewed-through-cli: 0.35.0 -->
|
|
13
13
|
|
|
14
14
|
# Neo Compose CLI
|
|
15
15
|
|
|
@@ -47,9 +47,8 @@ Treat pull followed immediately by status or dry-run as a semantic no-op. If it
|
|
|
47
47
|
reports project changes, stop and report a round-trip bug instead of pushing.
|
|
48
48
|
|
|
49
49
|
The compiler reports file:line:column diagnostics and fails closed. Unknown
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
value is exactly JSON `null` is treated as absent.
|
|
50
|
+
authorable server fields require a CLI contract update; never preserve them as
|
|
51
|
+
opaque JSON or guessed syntax.
|
|
53
52
|
|
|
54
53
|
## Honor hard authoring invariants
|
|
55
54
|
|
|
@@ -45,9 +45,12 @@ NeoVariant<ExampleObject> Down = new(
|
|
|
45
45
|
required: it is the only way to construct through the variant.
|
|
46
46
|
- `apply` takes exactly one parameter of the target class and is void. Omit it
|
|
47
47
|
for a variant that is only ever constructed.
|
|
48
|
-
- Both are ordinary delegate closures: parenthesized parameter list,
|
|
49
|
-
body
|
|
50
|
-
|
|
48
|
+
- Both are ordinary delegate closures: parenthesized parameter list, then
|
|
49
|
+
either a block body or an expression body. `initialize` returns, so
|
|
50
|
+
`() => new ExampleObject(.Down)` works; `apply` is void, so its expression
|
|
51
|
+
body must be a call or an assignment, as in
|
|
52
|
+
`(source) => source.FacingDir = .Down`. The server compiles them, so authored
|
|
53
|
+
source carries only the code.
|
|
51
54
|
|
|
52
55
|
Folders are records too, declared as their own globals and assigned per
|
|
53
56
|
variant. Nesting is spelled in the path with `/`; intermediate segments are
|
|
@@ -346,8 +349,11 @@ node bindings scoped to their node/children. Give each persisted condition use,
|
|
|
346
349
|
action invocation, mutation, pause, option, and node its own owner-scoped ID.
|
|
347
350
|
|
|
348
351
|
Use `=> Next` or a trailing block with `return Next;`; do not author a `to:`
|
|
349
|
-
argument.
|
|
350
|
-
|
|
352
|
+
argument. A destination arrow always follows an `=` initializer, as in
|
|
353
|
+
`= new(...) => Welcome;`; an arrow that instead follows a member name or a
|
|
354
|
+
parameter list's `)` is a NeoScript expression body, so the two never collide.
|
|
355
|
+
Require destinations for triggers, options, and outcomes. Permit terminal
|
|
356
|
+
text/actions to fall through. Keep all statements for
|
|
351
357
|
`Actions Empty = new();` inside its body.
|
|
352
358
|
|
|
353
359
|
After editing, run `neo dialogue dryrun <dialogue-ref>`. Exit 1 means the graph
|
|
@@ -83,7 +83,7 @@ wrappers.
|
|
|
83
83
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
84
84
|
|
|
85
85
|
```html
|
|
86
|
-
<!-- reviewed-through-cli: 0.
|
|
86
|
+
<!-- reviewed-through-cli: 0.35.0 -->
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -25,6 +25,41 @@ Declare a setter's value type explicitly with the canonical form
|
|
|
25
25
|
`set(int value) { ... }`. Project analysis, candidate materialization, and
|
|
26
26
|
focused script commands all use the same accessor extraction.
|
|
27
27
|
|
|
28
|
+
A body whose whole point is one expression may be written as an expression
|
|
29
|
+
body, `=> expr;`. It is pure sugar for the block form — same compiled
|
|
30
|
+
instructions, same diagnostics, same stored source — and is available on
|
|
31
|
+
getter-only properties, individual accessors, and functions:
|
|
32
|
+
|
|
33
|
+
```neo
|
|
34
|
+
public bool HasVisited => this.VisitCount > 0;
|
|
35
|
+
|
|
36
|
+
public int Clamped {
|
|
37
|
+
get => Math.Clamp(this.Raw, 0, 10);
|
|
38
|
+
set(int value) { this.Raw = value; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public bool Unlocked(int level) => this.Level >= level;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `=>` is only an expression body where it directly follows the member name
|
|
45
|
+
or the parameter list's `)`. An arrow after an `=` initializer is always a
|
|
46
|
+
NeoFlow destination, so `override Trigger Trigger = new(...) => Welcome;`
|
|
47
|
+
keeps its meaning.
|
|
48
|
+
|
|
49
|
+
In a void context — a `void` function, a setter, or a void delegate — `=> expr`
|
|
50
|
+
is an expression statement rather than a return, so the expression must do
|
|
51
|
+
something: a call such as `void Ping() => this.OnChanged();`, or an assignment
|
|
52
|
+
such as `set(int value) => this.Raw = value;`. An expression whose value would
|
|
53
|
+
simply be discarded is rejected with a diagnostic that names the block body as
|
|
54
|
+
the fix.
|
|
55
|
+
|
|
56
|
+
`neo push` and `neo pull` canonicalize a stored body to the shorthand when its
|
|
57
|
+
authored spelling is exactly one `return` statement that fits on one line;
|
|
58
|
+
anything else re-emits as a block. Setters always re-emit as blocks. A push
|
|
59
|
+
preserves the form you authored for members it did not otherwise rewrite, so
|
|
60
|
+
choosing longhand for a one-line getter is stable; a fresh `neo pull` writes
|
|
61
|
+
the canonical shorthand.
|
|
62
|
+
|
|
28
63
|
Declare a bodyless NeoScript contract with `abstract`, for example
|
|
29
64
|
`public abstract bool Equals(Item other);`. Reserve `native` for host-provided
|
|
30
65
|
functions; concrete NeoScript overrides inherit the abstract signature.
|
|
@@ -55,6 +90,15 @@ so later changes to the outer binding do not alter the closure. Explicit lambda
|
|
|
55
90
|
parameter annotations accept the same type grammar as declarations, including
|
|
56
91
|
`decimal`, generic parameters, collections, arrays, and nullable types.
|
|
57
92
|
|
|
93
|
+
A lambda may use an expression body too: `(x) => x * 2` is the block body
|
|
94
|
+
`(x) => { return x * 2; }`. In a void delegate the expression is a statement,
|
|
95
|
+
so it must call or assign. A dictionary-literal body needs parentheses,
|
|
96
|
+
`() => ({ "a": 1 })`, because a bare `{` after the arrow always opens a block.
|
|
97
|
+
|
|
98
|
+
```neo
|
|
99
|
+
List<Item> ready = this.Items.Where((item) => item.Count > 0);
|
|
100
|
+
```
|
|
101
|
+
|
|
58
102
|
Calling `Equals(other)` on a non-null generic value dynamically uses the
|
|
59
103
|
runtime Class's one-argument `Equals` Function or NSFunction when it returns
|
|
60
104
|
`bool`, including the effective override. Values without that member fall back
|