diff --git a/agent-notes/moo-expression-assignments.md b/agent-notes/moo-expression-assignments.md new file mode 100644 index 00000000..ca73695c --- /dev/null +++ b/agent-notes/moo-expression-assignments.md @@ -0,0 +1,20 @@ +# MOO expression assignments + +## Goal +Fix the false undefined-local warning for `if (valid(channel = this:current_channel(player)))` and push a client PR. Q authorized implementation with "MAKE IT SO!". ToastStunt at `~/src/toaststunt` is the syntax authority. + +## State +Branch `fix/moo-expression-assignments`, isolated from the user's dirty primary checkout, based on origin/master at 000c4b15. Scanner fix committed as d67759c5 and pushed. PR: https://github.com/MongooseMoo/react-client/pull/204. Scan the entire masked source with an identifier boundary and exclude property names. The normal commit hook completed typecheck and staged lint. Hosted validation is tracked on the PR's current head. + +## Decisions and why +Smallest change: approximately 20 lines in the existing assignment-definition collector. Scan masked source for identifier assignment targets regardless of expression punctuation or line breaks; exclude property names using the existing previous-character helper. No new parser or semantic architecture. + +ToastStunt `src/parser.y:139` accepts expressions in if conditions; `:466-486` constructs assignment expressions. The installed tree-sitter WASM parses the reported example into an assignment_expression with hasError false. Client `src/editor/moocode/semantics.ts:919` instead requires a preceding line start, whitespace or semicolon, missing the target immediately after `(`. Correct that existing rule first. + +No npm package change is justified by this example. Other reported issues have not yet been supplied. Do not publish an unchanged parser package. + +## Surprises +The warning is produced by the client's text-based semantic model, not tree-sitter. The current checkout is older than origin/master and contains unrelated untracked work. + +## Next step +Read PR #204's current hosted CI result and report it. No new guards or test files added, per Q's standing instruction. The requested client fix is pushed and the PR is open; no merge or release is authorized as part of this client PR request. Other parser issues await examples from Q. diff --git a/src/editor/moocode/semantics.ts b/src/editor/moocode/semantics.ts index f5ac26ea..7e79f45a 100644 --- a/src/editor/moocode/semantics.ts +++ b/src/editor/moocode/semantics.ts @@ -896,9 +896,9 @@ function collectDefinitions( const lines = masked.split(/\r\n|\r|\n/); const lineOffsets = getLineOffsets(source); + collectAssignmentDefinitions(source, masked, definitions); lines.forEach((line, lineIndex) => { const lineOffset = lineOffsets[lineIndex] ?? 0; - collectAssignmentDefinitions(source, line, lineOffset, definitions); collectForDefinitions(source, line, lineOffset, definitions); collectForkDefinitions(source, line, lineOffset, definitions); collectExceptDefinitions(source, line, lineOffset, definitions); @@ -911,21 +911,26 @@ function collectDefinitions( function collectAssignmentDefinitions( source: string, - line: string, - lineOffset: number, + masked: string, definitions: Array<{ name: string; occurrence: Occurrence }>, ): void { const assignmentPattern = new RegExp( - `(^|[;\\s])(${MOO_IDENTIFIER_PATTERN_SOURCE})\\s*=(?!=|>)`, + `\\b(${MOO_IDENTIFIER_PATTERN_SOURCE})\\s*=(?!=|>)`, 'g', ); - for (const match of line.matchAll(assignmentPattern)) { - const name = match[2]; + for (const match of masked.matchAll(assignmentPattern)) { + const name = match[1]; if (!name) { continue; } - const nameStart = lineOffset + match.index + match[0].indexOf(name); + // Assignment is an expression (ToastStunt parser.y), including in conditions + // and arguments. Property names are not local assignment targets. + const nameStart = match.index; + const previous = previousNonWhitespaceCharacter(masked, nameStart); + if (previous === '.' || previous === ':' || previous === '$') { + continue; + } definitions.push({ name, occurrence: occurrenceAt(source, nameStart, name.length),