better racket example support

This commit is contained in:
Dhanji R. Prasanna
2026-01-13 21:16:14 +05:30
parent c2f96d7048
commit 3a47ebe668
3 changed files with 287 additions and 6 deletions

View File

@@ -612,4 +612,117 @@ async fn test_racket_search() {
.filter_map(|m| m.captures.get("name").map(|s| s.as_str()))
.collect();
assert!(names.contains(&"greet"));
assert!(names.contains(&"add"));
assert!(names.contains(&"factorial"));
assert!(names.contains(&"person-greet"));
assert!(names.contains(&"describe-list"));
assert!(names.contains(&"sum-squares"));
}
#[tokio::test]
async fn test_racket_structs() {
// Get the workspace root (where Cargo.toml is)
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let workspace_root = std::path::Path::new(&manifest_dir)
.parent()
.and_then(|p| p.parent())
.unwrap();
let test_code_path = workspace_root.join("examples/test_code");
let request = CodeSearchRequest {
searches: vec![SearchSpec {
name: "racket_structs".to_string(),
query: r#"(list . (symbol) @kw (#eq? @kw "struct") . (symbol) @name)"#.to_string(),
language: "racket".to_string(),
paths: vec![test_code_path.to_string_lossy().to_string()],
context_lines: 0,
}],
max_concurrency: 4,
max_matches_per_search: 500,
};
let response = execute_code_search(request).await.unwrap();
assert_eq!(response.searches.len(), 1);
assert!(response.searches[0].matches.len() > 0);
// Should find person and point structs
let names: Vec<&str> = response.searches[0]
.matches
.iter()
.filter_map(|m| m.captures.get("name").map(|s| s.as_str()))
.collect();
assert!(names.contains(&"person"), "Should find 'person' struct, found: {:?}", names);
assert!(names.contains(&"point"), "Should find 'point' struct, found: {:?}", names);
}
#[tokio::test]
async fn test_racket_macros() {
// Get the workspace root (where Cargo.toml is)
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let workspace_root = std::path::Path::new(&manifest_dir)
.parent()
.and_then(|p| p.parent())
.unwrap();
let test_code_path = workspace_root.join("examples/test_code");
let request = CodeSearchRequest {
searches: vec![SearchSpec {
name: "racket_macros".to_string(),
query: r#"(list . (symbol) @kw (#eq? @kw "define-syntax-rule") . (list . (symbol) @name))"#.to_string(),
language: "racket".to_string(),
paths: vec![test_code_path.to_string_lossy().to_string()],
context_lines: 0,
}],
max_concurrency: 4,
max_matches_per_search: 500,
};
let response = execute_code_search(request).await.unwrap();
assert_eq!(response.searches.len(), 1);
assert!(response.searches[0].matches.len() > 0, "Should find macros, error: {:?}", response.searches[0].error);
// Should find swap! and unless macros
let names: Vec<&str> = response.searches[0]
.matches
.iter()
.filter_map(|m| m.captures.get("name").map(|s| s.as_str()))
.collect();
assert!(names.contains(&"swap!"), "Should find 'swap!' macro, found: {:?}", names);
assert!(names.contains(&"unless"), "Should find 'unless' macro, found: {:?}", names);
}
#[tokio::test]
async fn test_racket_contracts() {
// Get the workspace root (where Cargo.toml is)
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let workspace_root = std::path::Path::new(&manifest_dir)
.parent()
.and_then(|p| p.parent())
.unwrap();
let test_code_path = workspace_root.join("examples/test_code");
let request = CodeSearchRequest {
searches: vec![SearchSpec {
name: "racket_contracts".to_string(),
query: r#"(list . (symbol) @kw (#eq? @kw "define/contract") . (list . (symbol) @name))"#.to_string(),
language: "racket".to_string(),
paths: vec![test_code_path.to_string_lossy().to_string()],
context_lines: 0,
}],
max_concurrency: 4,
max_matches_per_search: 500,
};
let response = execute_code_search(request).await.unwrap();
assert_eq!(response.searches.len(), 1);
assert!(response.searches[0].matches.len() > 0, "Should find contract functions, error: {:?}", response.searches[0].error);
// Should find safe-divide and non-negative-add
let names: Vec<&str> = response.searches[0]
.matches
.iter()
.filter_map(|m| m.captures.get("name").map(|s| s.as_str()))
.collect();
assert!(names.contains(&"safe-divide"), "Should find 'safe-divide', found: {:?}", names);
assert!(names.contains(&"non-negative-add"), "Should find 'non-negative-add', found: {:?}", names);
}

View File

@@ -40,7 +40,8 @@ g3 includes a syntax-aware code search tool powered by tree-sitter. Unlike text-
- Java
- C
- C++
- Kotlin
- Racket
- Scheme
## Basic Usage
@@ -231,6 +232,65 @@ Tree-sitter queries use S-expression syntax. The basic pattern is:
(namespace_definition name: (identifier) @name)
```
### Racket
Racket uses an S-expression grammar where code is represented as nested lists. The tree-sitter-racket parser represents most forms as `(list (symbol) ...)` nodes.
```lisp
;; Function definitions: (define (name args...) body)
(list . (symbol) @kw (#eq? @kw "define") . (list . (symbol) @name))
;; Variable definitions: (define name value)
(list . (symbol) @kw (#eq? @kw "define") . (symbol) @name)
;; Struct definitions: (struct name (fields...))
(list . (symbol) @kw (#eq? @kw "struct") . (symbol) @name)
;; Lambda expressions
(list . (symbol) @kw (#match? @kw "^(lambda|λ)$"))
;; Let bindings
(list . (symbol) @kw (#match? @kw "^(let|let\\*|letrec)$"))
;; Require statements
(list . (symbol) @kw (#eq? @kw "require"))
;; Provide statements
(list . (symbol) @kw (#eq? @kw "provide"))
;; Module definitions
(list . (symbol) @kw (#match? @kw "^module"))
;; Contracts
(list . (symbol) @kw (#eq? @kw "define/contract") . (list . (symbol) @name))
;; Macros
(list . (symbol) @kw (#match? @kw "^(define-syntax|define-syntax-rule)$") . (symbol) @name)
;; For loops
(list . (symbol) @kw (#match? @kw "^for"))
;; Match expressions
(list . (symbol) @kw (#eq? @kw "match"))
;; Class definitions
(list . (symbol) @kw (#match? @kw "^class"))
```
**Note**: The `.` (dot) in queries like `(list . (symbol))` means "first child" - it matches the symbol that appears immediately after the opening parenthesis.
### Scheme
Scheme uses similar patterns to Racket:
```lisp
;; Function definitions
(list . (symbol) @kw (#eq? @kw "define") . (list . (symbol) @name))
;; Lambda expressions
(list . (symbol) @kw (#eq? @kw "lambda"))
```
## Advanced Queries
### Wildcards

View File

@@ -1,5 +1,11 @@
#lang racket
;; ============================================
;; Example Racket file for g3 code search tests
;; ============================================
;; --- Basic Functions ---
(define (greet name)
(printf "Hello, ~a!\n" name))
@@ -11,14 +17,116 @@
1
(* n (factorial (- n 1)))))
;; --- Variable Definitions ---
(define pi 3.14159)
(define greeting "Hello, World!")
;; --- Structs ---
(struct person (name age) #:transparent)
(struct point (x y) #:transparent)
(define (person-greet p)
(printf "Hello, I'm ~a\n" (person-name p)))
;; --- Pattern Matching ---
(define (describe-list lst)
(match lst
['() "empty"]
[(list x) (format "singleton: ~a" x)]
[(list x y) (format "pair: ~a, ~a" x y)]
[_ "many elements"]))
(define (point-quadrant p)
(match p
[(point (? positive?) (? positive?)) 'first]
[(point (? negative?) (? positive?)) 'second]
[(point (? negative?) (? negative?)) 'third]
[(point (? positive?) (? negative?)) 'fourth]
[_ 'origin-or-axis]))
;; --- Lambda and Higher-Order Functions ---
(define double (lambda (x) (* x 2)))
(define triple (λ (x) (* x 3)))
(define (apply-twice f x)
(f (f x)))
;; --- Let Bindings ---
(define (circle-area radius)
(let ([pi 3.14159])
(* pi radius radius)))
(define (swap-and-sum a b)
(let* ([temp a]
[a b]
[b temp])
(+ a b)))
;; --- For Loops ---
(define (sum-squares n)
(for/sum ([i (in-range 1 (add1 n))])
(* i i)))
(define (collect-evens n)
(for/list ([i (in-range n)]
#:when (even? i))
i))
(define (matrix-coords rows cols)
(for*/list ([r (in-range rows)]
[c (in-range cols)])
(cons r c)))
;; --- Macros ---
(define-syntax-rule (swap! x y)
(let ([tmp x])
(set! x y)
(set! y tmp)))
(define-syntax-rule (unless condition body ...)
(when (not condition)
body ...))
;; --- Contracts ---
(define/contract (safe-divide x y)
(-> number? (and/c number? (not/c zero?)) number?)
(/ x y))
(define/contract (non-negative-add a b)
(-> (>=/c 0) (>=/c 0) (>=/c 0))
(+ a b))
;; --- Require and Provide ---
(require racket/string)
(require racket/list)
;; --- Module ---
(module+ test
(require rackunit)
(check-equal? (add 2 3) 5)
(check-equal? (factorial 5) 120)
(check-equal? (sum-squares 3) 14)
(check-equal? (describe-list '()) "empty")
(check-equal? (describe-list '(1)) "singleton: 1"))
(module+ main
(greet "World")
(displayln (add 5 3))
(displayln (factorial 5))
(define alice (person "Alice" 30))
(person-greet alice)
(displayln (sum-squares 10))
(displayln (collect-evens 10)))