refactor(parser): move pendingIdentifiers and isInParams into Scope class
Replace module-level mutable state with immutable state managed within the Scope class itself. This eliminates state leakage between parser invocations and makes the code more functional and predictable. Changes: - Add pendingIdentifiers and isInParams as Scope constructor parameters - Add helper methods: withPendingIdentifiers(), withIsInParams(), clearPending() - Update hash() to include new state fields - Convert all mutable state operations to return new Scope instances - Remove module-level variables entirely Benefits: - No state leakage between tests or parser invocations - Easier to reason about - state is explicit in the context - More functional programming style with immutable updates - Eliminates entire class of bugs related to stale module state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a33f6cd191
commit
a652f83b63
|
|
@ -2,7 +2,12 @@ import { ContextTracker } from '@lezer/lr'
|
||||||
import * as terms from './shrimp.terms'
|
import * as terms from './shrimp.terms'
|
||||||
|
|
||||||
export class Scope {
|
export class Scope {
|
||||||
constructor(public parent: Scope | null, public vars: Set<string>) {}
|
constructor(
|
||||||
|
public parent: Scope | null,
|
||||||
|
public vars: Set<string>,
|
||||||
|
public pendingIdentifiers: string[] = [],
|
||||||
|
public isInParams: boolean = false
|
||||||
|
) {}
|
||||||
|
|
||||||
has(name: string): boolean {
|
has(name: string): boolean {
|
||||||
return this.vars.has(name) ?? this.parent?.has(name)
|
return this.vars.has(name) ?? this.parent?.has(name)
|
||||||
|
|
@ -11,15 +16,27 @@ export class Scope {
|
||||||
add(...names: string[]): Scope {
|
add(...names: string[]): Scope {
|
||||||
const newVars = new Set(this.vars)
|
const newVars = new Set(this.vars)
|
||||||
names.forEach((name) => newVars.add(name))
|
names.forEach((name) => newVars.add(name))
|
||||||
return new Scope(this.parent, newVars)
|
return new Scope(this.parent, newVars, [], this.isInParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
push(): Scope {
|
push(): Scope {
|
||||||
return new Scope(this, new Set())
|
return new Scope(this, new Set(), [], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pop(): Scope {
|
pop(): Scope {
|
||||||
return this.parent ?? new Scope(null, new Set())
|
return this.parent ?? new Scope(null, new Set(), [], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
withPendingIdentifiers(ids: string[]): Scope {
|
||||||
|
return new Scope(this.parent, this.vars, ids, this.isInParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
withIsInParams(value: boolean): Scope {
|
||||||
|
return new Scope(this.parent, this.vars, this.pendingIdentifiers, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPending(): Scope {
|
||||||
|
return new Scope(this.parent, this.vars, [], this.isInParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash(): number {
|
hash(): number {
|
||||||
|
|
@ -34,23 +51,21 @@ export class Scope {
|
||||||
h = (h << 5) - h + this.parent.hash()
|
h = (h << 5) - h + this.parent.hash()
|
||||||
h |= 0
|
h |= 0
|
||||||
}
|
}
|
||||||
|
// Include pendingIdentifiers and isInParams in hash
|
||||||
|
h = (h << 5) - h + this.pendingIdentifiers.length
|
||||||
|
h = (h << 5) - h + (this.isInParams ? 1 : 0)
|
||||||
|
h |= 0
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module-level state for tracking identifiers
|
|
||||||
let pendingIdentifiers: string[] = []
|
|
||||||
let isInParams = false
|
|
||||||
|
|
||||||
export const trackScope = new ContextTracker<Scope>({
|
export const trackScope = new ContextTracker<Scope>({
|
||||||
start: new Scope(null, new Set()),
|
start: new Scope(null, new Set(), [], false),
|
||||||
|
|
||||||
shift(context, term, stack, input) {
|
shift(context, term, stack, input) {
|
||||||
// Track fn keyword to enter param capture mode
|
// Track fn keyword to enter param capture mode
|
||||||
if (term === terms.Fn) {
|
if (term === terms.Fn) {
|
||||||
isInParams = true
|
return context.withIsInParams(true).withPendingIdentifiers([])
|
||||||
pendingIdentifiers = []
|
|
||||||
return context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture identifiers
|
// Capture identifiers
|
||||||
|
|
@ -66,14 +81,13 @@ export const trackScope = new ContextTracker<Scope>({
|
||||||
text += String.fromCharCode(ch)
|
text += String.fromCharCode(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Capture ALL identifiers when in params
|
// Capture ALL identifiers when in params
|
||||||
if (isInParams) {
|
if (context.isInParams) {
|
||||||
pendingIdentifiers.push(text)
|
return context.withPendingIdentifiers([...context.pendingIdentifiers, text])
|
||||||
}
|
}
|
||||||
// Capture FIRST identifier for assignments
|
// Capture FIRST identifier for assignments
|
||||||
else if (pendingIdentifiers.length === 0) {
|
else if (context.pendingIdentifiers.length === 0) {
|
||||||
pendingIdentifiers.push(text)
|
return context.withPendingIdentifiers([text])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,23 +96,17 @@ export const trackScope = new ContextTracker<Scope>({
|
||||||
|
|
||||||
reduce(context, term, stack, input) {
|
reduce(context, term, stack, input) {
|
||||||
// Add assignment variable to scope
|
// Add assignment variable to scope
|
||||||
if (term === terms.Assign && pendingIdentifiers.length > 0) {
|
if (term === terms.Assign && context.pendingIdentifiers.length > 0) {
|
||||||
const newContext = context.add(pendingIdentifiers[0]!)
|
return context.add(context.pendingIdentifiers[0]!)
|
||||||
pendingIdentifiers = []
|
|
||||||
return newContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push new scope and add parameters
|
// Push new scope and add parameters
|
||||||
if (term === terms.Params) {
|
if (term === terms.Params) {
|
||||||
const newScope = context.push()
|
const newScope = context.push()
|
||||||
if (pendingIdentifiers.length > 0) {
|
if (context.pendingIdentifiers.length > 0) {
|
||||||
const newContext = newScope.add(...pendingIdentifiers)
|
return newScope.add(...context.pendingIdentifiers).withIsInParams(false)
|
||||||
pendingIdentifiers = []
|
|
||||||
isInParams = false
|
|
||||||
return newContext
|
|
||||||
}
|
}
|
||||||
isInParams = false
|
return newScope.withIsInParams(false)
|
||||||
return newScope
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop scope when exiting function
|
// Pop scope when exiting function
|
||||||
|
|
@ -108,7 +116,7 @@ export const trackScope = new ContextTracker<Scope>({
|
||||||
|
|
||||||
// Clear stale identifiers after non-assignment statements
|
// Clear stale identifiers after non-assignment statements
|
||||||
if (term === terms.DotGet || term === terms.FunctionCallOrIdentifier || term === terms.FunctionCall) {
|
if (term === terms.DotGet || term === terms.FunctionCallOrIdentifier || term === terms.FunctionCall) {
|
||||||
pendingIdentifiers = []
|
return context.clearPending()
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user