From 283ab1b39fb851e91821f9207fa1d445e5a79d93 Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Fri, 22 May 2026 12:51:20 -0700 Subject: [PATCH] refactor: convert grammar predicate detection to spec table Replace 21-item hardcoded isKeywordThatCanBePredicate array with isPredicateName() from PredicateSpec (+ 'size' special case for fluent API FOL bodies). Replace 24-item parseRelation relationKinds array with collectSpatialPredicateNames() from spec. Error message now also auto-derives from the spec. 595 SDK + 57 E2E tests pass. --- packages/imhotep-dsl/src/grammar.ts | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/imhotep-dsl/src/grammar.ts b/packages/imhotep-dsl/src/grammar.ts index 25e8afe..b8c2fdc 100644 --- a/packages/imhotep-dsl/src/grammar.ts +++ b/packages/imhotep-dsl/src/grammar.ts @@ -27,6 +27,8 @@ import type { ToleranceLiteralNode, } from 'imhotep-core' +import { isPredicateName, collectSpatialPredicateNames } from 'imhotep-core' + import type { Token } from './lexer.js' // --------------------------------------------------------------------------- @@ -658,16 +660,9 @@ export class GrammarParser { private isKeywordThatCanBePredicate(): boolean { const kind = this.currentToken().kind - const predicateKinds: Token['kind'][] = [ - 'leftOf', 'rightOf', 'above', 'below', - 'alignedWith', 'leftAlignedWith', 'rightAlignedWith', 'topAlignedWith', 'bottomAlignedWith', - 'centeredWithin', 'inside', 'contains', 'overlaps', 'intersects', 'touches', 'separatedFrom', 'hasGap', - // Spatial aliases - 'beside', 'nextTo', 'adjacent', 'touching', 'near', 'under', 'within', - // Size predicates that can appear in FOL formula bodies - 'width', 'height', 'size', 'between', - ] - return predicateKinds.includes(kind) + // 'size' is a fluent API keyword for size assertions in FOL bodies; + // all other predicate names derive from the spec table. + return isPredicateName(kind) || kind === 'size' } // ------------------------------------------------------------------------- @@ -1142,13 +1137,7 @@ export class GrammarParser { ) } - const relationKinds: Array = [ - 'leftOf', 'rightOf', 'above', 'below', - 'alignedWith', 'leftAlignedWith', 'rightAlignedWith', 'topAlignedWith', 'bottomAlignedWith', - 'centeredWithin', 'inside', 'contains', 'overlaps', 'intersects', 'touches', 'separatedFrom', 'hasGap', - // Spatial aliases - 'beside', 'nextTo', 'adjacent', 'touching', 'near', 'under', 'within', - ] + const relationKinds = collectSpatialPredicateNames() as Array for (const kind of relationKinds) { if (this.match(kind)) { @@ -1156,7 +1145,7 @@ export class GrammarParser { } } - throw this.error(`Expected relation (leftOf, rightOf, above, below, alignedWith, centeredWithin, inside, contains, overlaps, intersects, touches, separatedFrom, hasGap, beside, nextTo, adjacent, touching, near, under, within)`) + throw this.error(`Expected relation (${relationKinds.join(', ')})`) } // -------------------------------------------------------------------------