···1313import type { Card } from "../scryfall-types";
1414import { type CardPredicate, compile } from "./matcher";
1515import { parse } from "./parser";
1616-import type { ParseError, Result, SearchNode } from "./types";
1616+import type { CompileError, ParseError, Result, SearchNode } from "./types";
17171818/**
1919 * Compiled search query
···2626}
27272828/**
2929+ * Search error - either a parse error or a compile error
3030+ */
3131+export type SearchError = ParseError | CompileError;
3232+3333+/**
2934 * Parse and compile a Scryfall search query
3035 *
3131- * Returns a Result with either a compiled search or a parse error.
3636+ * Returns a Result with either a compiled search or a parse/compile error.
3237 */
3333-export function search(query: string): Result<CompiledSearch> {
3838+export function search(query: string): Result<CompiledSearch, SearchError> {
3439 const parseResult = parse(query);
35403641 if (!parseResult.ok) {
···3843 }
39444045 const ast = parseResult.value;
4141- const match = compile(ast);
4646+ const compileResult = compile(ast);
4747+4848+ if (!compileResult.ok) {
4949+ return compileResult;
5050+ }
42514352 return {
4453 ok: true,
4545- value: { match, ast },
5454+ value: { match: compileResult.value, ast },
4655 };
4756}
4857···96105}
9710698107// Re-export types
9999-export type { SearchNode, Result, ParseError, CardPredicate };
108108+export type { SearchNode, Result, ParseError, CompileError, CardPredicate };
100109export type { CompiledSearch as SearchResult };
101110102111export { describeQuery } from "./describe";
+36-14
src/lib/search/matcher.ts
···55 */
6677import { type CardPredicate, compileField } from "./fields";
88-import type { SearchNode } from "./types";
88+import type { CompileError, Result, SearchNode } from "./types";
99+import { ok } from "./types";
9101011// Re-export CardPredicate for convenience
1112export type { CardPredicate };
···1314/**
1415 * Compile an AST node into a card predicate function
1516 */
1616-export function compile(node: SearchNode): CardPredicate {
1717+export function compile(node: SearchNode): Result<CardPredicate, CompileError> {
1718 switch (node.type) {
1819 case "AND":
1920 return compileAnd(node.children);
···2526 return compileNot(node.child);
26272728 case "FIELD":
2828- return compileField(node.field, node.operator, node.value);
2929+ return compileField(node.field, node.operator, node.value, node.span);
29303031 case "NAME":
3131- return compileName(node.value, node.pattern);
3232+ return ok(compileName(node.value, node.pattern));
32333334 case "EXACT_NAME":
3434- return compileExactName(node.value);
3535+ return ok(compileExactName(node.value));
3536 }
3637}
37383839/**
3940 * Compile AND node - all children must match
4041 */
4141-function compileAnd(children: SearchNode[]): CardPredicate {
4242- const predicates = children.map(compile);
4343- return (card) => predicates.every((p) => p(card));
4242+function compileAnd(
4343+ children: SearchNode[],
4444+): Result<CardPredicate, CompileError> {
4545+ const predicates: CardPredicate[] = [];
4646+ for (const child of children) {
4747+ const result = compile(child);
4848+ if (!result.ok) {
4949+ return result;
5050+ }
5151+ predicates.push(result.value);
5252+ }
5353+ return ok((card) => predicates.every((p) => p(card)));
4454}
45554656/**
4757 * Compile OR node - any child must match
4858 */
4949-function compileOr(children: SearchNode[]): CardPredicate {
5050- const predicates = children.map(compile);
5151- return (card) => predicates.some((p) => p(card));
5959+function compileOr(
6060+ children: SearchNode[],
6161+): Result<CardPredicate, CompileError> {
6262+ const predicates: CardPredicate[] = [];
6363+ for (const child of children) {
6464+ const result = compile(child);
6565+ if (!result.ok) {
6666+ return result;
6767+ }
6868+ predicates.push(result.value);
6969+ }
7070+ return ok((card) => predicates.some((p) => p(card)));
5271}
53725473/**
5574 * Compile NOT node - child must not match
5675 */
5757-function compileNot(child: SearchNode): CardPredicate {
5858- const predicate = compile(child);
5959- return (card) => !predicate(card);
7676+function compileNot(child: SearchNode): Result<CardPredicate, CompileError> {
7777+ const result = compile(child);
7878+ if (!result.ok) {
7979+ return result;
8080+ }
8181+ return ok((card) => !result.value(card));
6082}
61836284/**
+9
src/lib/search/types.ts
···269269}
270270271271/**
272272+ * Compile error (semantic error during AST compilation)
273273+ * Doesn't include input since the caller has it
274274+ */
275275+export interface CompileError {
276276+ message: string;
277277+ span: Span;
278278+}
279279+280280+/**
272281 * Result type for fallible operations
273282 */
274283export type Result<T, E = ParseError> =