/ src / engine / parser.ts
parser.ts
 1  /**
 2   * Command parser for Dunnet
 3   */
 4  
 5  export interface ParsedCommand {
 6    verb: string;
 7    args: string[];
 8  }
 9  
10  const IGNORED_WORDS = ['the', 'to', 'at', 'a', 'an', 'some'];
11  
12  export function parseCommand(input: string): ParsedCommand | null {
13    const trimmed = input.trim().toLowerCase();
14    if (!trimmed) return null;
15    
16    // Tokenize
17    const words = trimmed.split(/[ ,:;]+/).filter(w => w.length > 0);
18    
19    if (words.length === 0) return null;
20    
21    // Remove ignored words
22    const filtered = words.filter(w => !IGNORED_WORDS.includes(w));
23    
24    if (filtered.length === 0) return null;
25    
26    return {
27      verb: filtered[0],
28      args: filtered.slice(1)
29    };
30  }
31  
32  export function parseTwoWordCommand(input: string): { direct: string | null; indirect: string | null } {
33    // Handle "put X in/into/on Y" or "put X in Y"
34    const trimmed = input.trim().toLowerCase();
35    
36    // Match patterns like "put diamond in jar", "put key in box", "put weight on button"
37    const putMatch = trimmed.match(/^(?:put|insert|place)\s+(.+?)\s+(?:in|into|on|onto)\s+(.+)$/);
38    if (putMatch) {
39      return { direct: putMatch[1], indirect: putMatch[2] };
40    }
41    
42    // Simple space split for other cases
43    const words = trimmed.split(/\s+/).filter(w => w.length > 0 && !IGNORED_WORDS.includes(w));
44    
45    if (words.length >= 2) {
46      return { direct: words[1], indirect: null };
47    }
48    
49    return { direct: words[1] || null, indirect: null };
50  }
51  
52  export function matchObject(query: string, objectNames: Map<string, number>): number | null {
53    const normalized = query.replace(/\s+/g, '').toLowerCase();
54    
55    // Direct match
56    if (objectNames.has(normalized)) {
57      return objectNames.get(normalized)!;
58    }
59    
60    // Partial match (first attempt)
61    for (const [name, id] of objectNames) {
62      if (name.includes(normalized) || normalized.includes(name)) {
63        return id;
64      }
65    }
66    
67    // Try matching just the first word
68    const firstWord = normalized.split(/\s+/)[0];
69    for (const [name, id] of objectNames) {
70      if (name.startsWith(firstWord)) {
71        return id;
72      }
73    }
74    
75    return null;
76  }