refactor: eliminate remaining hardcoded predicate-name dispatch
Extraction.ts (3 fixes):
- Replace 2 'inStackingContext' string checks with isVariableArityPredicate()
- Replace 7-name diagnostic formatting if/else with spec-driven
getPredicateSpec() checks (isDirectional → gap message,
isSize → threshold hint, else generic)
Grammar.ts: Replace 8 hardcoded parser routing checks
(atLeast/atMost/aspectRatio/between/clippedBy/attachedToScrollContainer/
escapeClippingChainOf/inStackingContext) with SIZE_PREDICATE_NAMES and
TOPOLOGY_PREDICATE_NAMES Sets derived from spec table.
Pipeline.ts: Replace 15-entry CODE_TO_CLAUSE_KIND map with runtime
generation from PREDICATE_SPECS. Prefix derived from spec.isSize
('size.*') / validOptions.includes('axis') ('alignment.*') /
else ('relation.*'). Manual override for aspectRatio code 15.
Proofs.ts: Replace 11-case switch(kind) with 5 spec-driven if/else
branches categorized by validOptions presence (hasGap→directional,
hasAxis→alignment) + 2 specific name checks (inside overflow,
aspectRatio ratio). 11 predicate names → 0 hardcoded.
Lexer.ts: Export KEYWORDS map for conformance testing.
Conformance tests:
- Solver: every BUILTIN_PREDICATES entry matches its PREDICATE_SPECS
counterpart; every spec name (incl. aliases) has a registered
evaluator with matching descriptor (2 tests)
- DSL: every predicate name from collectAllPredicateNames() appears
in the lexer KEYWORDS table (1 test)
598 SDK + 3 conformance + 57 E2E = 658 tests pass.
This commit is contained in:
@@ -56,6 +56,8 @@ import {
|
||||
getPredicateDiagnosticCode,
|
||||
getPredicateDecomposition,
|
||||
isUnaryPredicate,
|
||||
isVariableArityPredicate,
|
||||
getPredicateSpec,
|
||||
} from 'imhotep-core'
|
||||
import { buildGeometryWorld } from './world-builder.js'
|
||||
import {
|
||||
@@ -1206,7 +1208,7 @@ export function compileCanonicalClauseToFormula(clause: CanonicalClauseDescripto
|
||||
}
|
||||
} else {
|
||||
const unaryPredicate = isUnaryPredicate(clause.relation)
|
||||
&& !(clause.relation === 'inStackingContext' && clause.reference)
|
||||
&& !(isVariableArityPredicate(clause.relation) && clause.reference)
|
||||
|
||||
body = {
|
||||
type: 'FormulaNode',
|
||||
@@ -1251,7 +1253,7 @@ export function compileCanonicalClauseToFormula(clause: CanonicalClauseDescripto
|
||||
}
|
||||
|
||||
const isUnary = isUnaryPredicate(clause.relation)
|
||||
&& !(clause.relation === 'inStackingContext' && clause.reference)
|
||||
&& !(isVariableArityPredicate(clause.relation) && clause.reference)
|
||||
|
||||
if (isUnary) {
|
||||
return {
|
||||
@@ -1645,19 +1647,12 @@ export function mapFolDiagnostic(
|
||||
: 'min'
|
||||
const expectedGap = gapKind === 'min' ? (minGap ?? 0) : (maxGap ?? 0)
|
||||
const boundDescription = gapKind === 'min' ? 'minimum required gap' : 'maximum required gap'
|
||||
if (predicateName === 'leftOf' && observedGap !== undefined) {
|
||||
message = `leftOf assertion failed: measured gap is ${observedGap.toFixed(0)}px, but ${boundDescription} is ${expectedGap}px.`
|
||||
const spec = predicateName ? getPredicateSpec(predicateName) : undefined
|
||||
const isDirectional = spec?.validOptions.includes('minGap') ?? false
|
||||
if (isDirectional && observedGap !== undefined) {
|
||||
message = `${predicateName} assertion failed: measured gap is ${observedGap.toFixed(0)}px, but ${boundDescription} is ${expectedGap}px.`
|
||||
fixHints.push(`The measured gap is ${observedGap.toFixed(0)}px. Consider ${gapKind === 'min' ? 'lowering minGap' : 'increasing maxGap'} or checking element positions.`)
|
||||
} else if (predicateName === 'above' && observedGap !== undefined) {
|
||||
message = `above assertion failed: measured gap is ${observedGap.toFixed(0)}px, but ${boundDescription} is ${expectedGap}px.`
|
||||
fixHints.push(`The measured gap is ${observedGap.toFixed(0)}px. Consider ${gapKind === 'min' ? 'lowering minGap' : 'increasing maxGap'} or checking element positions.`)
|
||||
} else if (predicateName === 'below' && observedGap !== undefined) {
|
||||
message = `below assertion failed: measured gap is ${observedGap.toFixed(0)}px, but ${boundDescription} is ${expectedGap}px.`
|
||||
fixHints.push(`The measured gap is ${observedGap.toFixed(0)}px. Consider ${gapKind === 'min' ? 'lowering minGap' : 'increasing maxGap'} or checking element positions.`)
|
||||
} else if (predicateName === 'rightOf' && observedGap !== undefined) {
|
||||
message = `rightOf assertion failed: measured gap is ${observedGap.toFixed(0)}px, but ${boundDescription} is ${expectedGap}px.`
|
||||
fixHints.push(`The measured gap is ${observedGap.toFixed(0)}px. Consider ${gapKind === 'min' ? 'lowering minGap' : 'increasing maxGap'} or checking element positions.`)
|
||||
} else if (predicateName === 'atLeast' || predicateName === 'atMost' || predicateName === 'between') {
|
||||
} else if (spec?.isSize) {
|
||||
fixHints.push(`Check the expected size threshold and the actual element dimensions using ui.extract(selector).`)
|
||||
} else {
|
||||
fixHints.push(`Verify the expected layout and consider adjusting thresholds.`)
|
||||
|
||||
Reference in New Issue
Block a user