refactor(scope): simplify trackScope to only track AssignableIdentifier

- Update trackScope ContextTracker to use ScopeContext wrapper
- Simplify shift() to only capture AssignableIdentifier tokens
- Simplify reduce() to handle only Assign, Params, and FunctionDef
- Update hash function to use hashScope helper
- Export ScopeContext class for use in tokenizer
- Update tokenizer to access scope via ScopeContext.scope

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Corey Johnson 2025-10-17 18:43:11 -07:00
parent 7de1682e91
commit aee9fa0747
2 changed files with 29 additions and 35 deletions

View File

@ -42,7 +42,7 @@ export class Scope {
} }
// Wrapper that adds temporary state for identifier capture // Wrapper that adds temporary state for identifier capture
class ScopeContext { export class ScopeContext {
constructor( constructor(
public scope: Scope, public scope: Scope,
public pendingIds: string[] = [] public pendingIds: string[] = []
@ -54,17 +54,12 @@ const hashScope = (context: ScopeContext): number => {
return context.scope.hash() return context.scope.hash()
} }
export const trackScope = new ContextTracker<Scope>({ export const trackScope = new ContextTracker<ScopeContext>({
start: new Scope(null, new Set(), [], false), start: new ScopeContext(new Scope(null, new Set())),
shift(context, term, stack, input) { shift(context, term, stack, input) {
// Track fn keyword to enter param capture mode // Only capture AssignableIdentifier tokens
if (term === terms.Fn) { if (term === terms.AssignableIdentifier) {
return context.withIsInParams(true).withPendingIdentifiers([])
}
// Capture identifiers
if (term === terms.Identifier) {
// Build text by peeking backwards from stack.pos to input.pos // Build text by peeking backwards from stack.pos to input.pos
let text = '' let text = ''
const start = input.pos const start = input.pos
@ -76,14 +71,10 @@ export const trackScope = new ContextTracker<Scope>({
text += String.fromCharCode(ch) text += String.fromCharCode(ch)
} }
// Capture ALL identifiers when in params return new ScopeContext(
if (context.isInParams) { context.scope,
return context.withPendingIdentifiers([...context.pendingIdentifiers, text]) [...context.pendingIds, text]
} )
// Capture FIRST identifier for assignments
else if (context.pendingIdentifiers.length === 0) {
return context.withPendingIdentifiers([text])
}
} }
return context return context
@ -91,31 +82,33 @@ 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 && context.pendingIdentifiers.length > 0) { if (term === terms.Assign && context.pendingIds.length > 0) {
return context.add(context.pendingIdentifiers[0]!) // Pop the last identifier (most recent AssignableIdentifier)
const varName = context.pendingIds[context.pendingIds.length - 1]!
return new ScopeContext(
context.scope.add(varName),
context.pendingIds.slice(0, -1)
)
} }
// Push new scope and add parameters // Push new scope and add all parameters
if (term === terms.Params) { if (term === terms.Params) {
const newScope = context.push() const newScope = context.scope.push()
if (context.pendingIdentifiers.length > 0) { return new ScopeContext(
return newScope.add(...context.pendingIdentifiers).withIsInParams(false) context.pendingIds.length > 0
} ? newScope.add(...context.pendingIds)
return newScope.withIsInParams(false) : newScope,
[] // Clear all pending after consuming
)
} }
// Pop scope when exiting function // Pop scope when exiting function
if (term === terms.FunctionDef) { if (term === terms.FunctionDef) {
return context.pop() return new ScopeContext(context.scope.pop(), [])
}
// Clear stale identifiers after non-assignment statements
if (term === terms.DotGet || term === terms.FunctionCallOrIdentifier || term === terms.FunctionCall) {
return context.clearPending()
} }
return context return context
}, },
hash: (context) => context.hash(), hash: hashScope,
}) })

View File

@ -1,6 +1,6 @@
import { ExternalTokenizer, InputStream, Stack } from '@lezer/lr' import { ExternalTokenizer, InputStream, Stack } from '@lezer/lr'
import { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot } from './shrimp.terms' import { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot } from './shrimp.terms'
import type { Scope } from './scopeTracker' import type { ScopeContext } from './scopeTracker'
// The only chars that can't be words are whitespace, apostrophes, closing parens, and EOF. // The only chars that can't be words are whitespace, apostrophes, closing parens, and EOF.
@ -36,7 +36,8 @@ export const tokenizer = new ExternalTokenizer(
identifierText += String.fromCharCode(charCode) identifierText += String.fromCharCode(charCode)
} }
const scope = stack.context as Scope | undefined const scopeContext = stack.context as ScopeContext | undefined
const scope = scopeContext?.scope
if (scope?.has(identifierText)) { if (scope?.has(identifierText)) {
// In scope - stop here, let grammar parse property access // In scope - stop here, let grammar parse property access