workin'
This commit is contained in:
parent
78ae96fc72
commit
0f7d3126a2
|
|
@ -5,21 +5,7 @@ export class Scope {
|
||||||
constructor(public parent: Scope | null, public vars = new Set<string>()) {}
|
constructor(public parent: Scope | null, public vars = new Set<string>()) {}
|
||||||
|
|
||||||
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) ?? false)
|
||||||
}
|
|
||||||
|
|
||||||
add(...names: string[]): Scope {
|
|
||||||
const newVars = new Set(this.vars)
|
|
||||||
names.forEach((name) => newVars.add(name))
|
|
||||||
return new Scope(this.parent, newVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
push(): Scope {
|
|
||||||
return new Scope(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
pop(): Scope {
|
|
||||||
return this.parent ?? this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hash(): number {
|
hash(): number {
|
||||||
|
|
@ -36,35 +22,27 @@ export class Scope {
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Static methods that return new Scopes (immutable operations)
|
||||||
|
|
||||||
|
static add(scope: Scope, ...names: string[]): Scope {
|
||||||
|
const newVars = new Set(scope.vars)
|
||||||
|
names.forEach((name) => newVars.add(name))
|
||||||
|
return new Scope(scope.parent, newVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
push(): Scope {
|
||||||
|
return new Scope(this, new Set())
|
||||||
|
}
|
||||||
|
|
||||||
|
pop(): Scope {
|
||||||
|
return this.parent ?? this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper that adds temporary state for identifier capture
|
// Tracker context that combines Scope with temporary pending identifiers
|
||||||
export class ScopeContext {
|
class TrackerContext {
|
||||||
constructor(public scope: Scope, public pendingIds: string[] = []) {}
|
constructor(public scope: Scope, public pendingIds: string[] = []) {}
|
||||||
|
|
||||||
// Helper to append identifier to pending list
|
|
||||||
withPending(id: string): ScopeContext {
|
|
||||||
return new ScopeContext(this.scope, [...this.pendingIds, id])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to consume last pending identifier and add to scope
|
|
||||||
consumeLast(): ScopeContext {
|
|
||||||
const varName = this.pendingIds.at(-1)
|
|
||||||
if (!varName) return this
|
|
||||||
return new ScopeContext(this.scope.add(varName), this.pendingIds.slice(0, -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to consume all pending identifiers and add to new scope
|
|
||||||
consumeAll(): ScopeContext {
|
|
||||||
let newScope = this.scope.push()
|
|
||||||
newScope = this.pendingIds.length > 0 ? newScope.add(...this.pendingIds) : newScope
|
|
||||||
return new ScopeContext(newScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to clear pending without adding to scope
|
|
||||||
clearPending(): ScopeContext {
|
|
||||||
return new ScopeContext(this.scope)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract identifier text from input stream
|
// Extract identifier text from input stream
|
||||||
|
|
@ -79,23 +57,36 @@ const readIdentifierText = (input: InputStream, start: number, end: number): str
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
export const trackScope = new ContextTracker<ScopeContext>({
|
export const trackScope = new ContextTracker<TrackerContext>({
|
||||||
start: new ScopeContext(new Scope(null, new Set())),
|
start: new TrackerContext(new Scope(null, new Set())),
|
||||||
|
|
||||||
shift(context, term, stack, input) {
|
shift(context, term, stack, input) {
|
||||||
if (term !== terms.AssignableIdentifier) return context
|
if (term !== terms.AssignableIdentifier) return context
|
||||||
|
|
||||||
const text = readIdentifierText(input, input.pos, stack.pos)
|
const text = readIdentifierText(input, input.pos, stack.pos)
|
||||||
return context.withPending(text)
|
return new TrackerContext(context.scope, [...context.pendingIds, text])
|
||||||
},
|
},
|
||||||
|
|
||||||
reduce(context, term) {
|
reduce(context, term) {
|
||||||
if (term === terms.Assign) return context.consumeLast()
|
// Add assignment variable to scope
|
||||||
if (term === terms.Params) return context.consumeAll()
|
if (term === terms.Assign) {
|
||||||
|
const varName = context.pendingIds.at(-1)
|
||||||
|
if (!varName) return context
|
||||||
|
return new TrackerContext(Scope.add(context.scope, varName), context.pendingIds.slice(0, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push new scope and add all parameters
|
||||||
|
if (term === terms.Params) {
|
||||||
|
let newScope = context.scope.push()
|
||||||
|
if (context.pendingIds.length > 0) {
|
||||||
|
newScope = Scope.add(newScope, ...context.pendingIds)
|
||||||
|
}
|
||||||
|
return new TrackerContext(newScope, [])
|
||||||
|
}
|
||||||
|
|
||||||
// Pop scope when exiting function
|
// Pop scope when exiting function
|
||||||
if (term === terms.FunctionDef) {
|
if (term === terms.FunctionDef) {
|
||||||
return new ScopeContext(context.scope.pop())
|
return new TrackerContext(context.scope.pop(), [])
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
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 { 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.
|
||||||
|
|
||||||
|
|
@ -138,12 +137,11 @@ const consumeRestOfWord = (input: InputStream, startPos: number, canBeWord: bool
|
||||||
// Returns IdentifierBeforeDot token if in scope, null otherwise
|
// Returns IdentifierBeforeDot token if in scope, null otherwise
|
||||||
const checkForDotGet = (input: InputStream, stack: Stack, pos: number): number | null => {
|
const checkForDotGet = (input: InputStream, stack: Stack, pos: number): number | null => {
|
||||||
const identifierText = buildIdentifierText(input, pos)
|
const identifierText = buildIdentifierText(input, pos)
|
||||||
const scopeContext = stack.context as ScopeContext | undefined
|
const context = stack.context as { scope: { has(name: string): boolean } } | undefined
|
||||||
const scope = scopeContext?.scope
|
|
||||||
|
|
||||||
// If identifier is in scope, this is property access (e.g., obj.prop)
|
// If identifier is in scope, this is property access (e.g., obj.prop)
|
||||||
// If not in scope, it should be consumed as a Word (e.g., file.txt)
|
// If not in scope, it should be consumed as a Word (e.g., file.txt)
|
||||||
return scope?.has(identifierText) ? IdentifierBeforeDot : null
|
return context?.scope.has(identifierText) ? IdentifierBeforeDot : null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decide between AssignableIdentifier and Identifier using grammar state + peek-ahead
|
// Decide between AssignableIdentifier and Identifier using grammar state + peek-ahead
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user