feat: AI-assisted compilation and code generation

- New src/ai_assist.nim module with DeepSeek/OpenAI/MiMo API support
- AI explains compiler errors automatically when compilation fails
- New CLI command: cljnim ai '<description>' for code generation
- REPL :ai command for interactive AI assistance
- API keys read from environment vars (DEEPSEEK_API_KEY, OPENAI_API_KEY, MIMO_API_KEY)
- Tests for prompt building and response formatting
- Updated README with AI integration docs
This commit is contained in:
2026-05-08 22:21:27 +03:00
parent 6bed638300
commit 6249d8a251
7 changed files with 382 additions and 32 deletions
+57 -9
View File
@@ -1,6 +1,6 @@
# Clojure/Nim — AI-First Clojure Compiler
import os, osproc, strutils, times
import reader, emitter, types, repl, macros, deps
import reader, emitter, types, repl, macros, deps, ai_assist
proc getLibPath(): string =
let appDir = getAppDir()
@@ -44,7 +44,7 @@ proc extractRequires(forms: seq[CljVal]): seq[(string, string)] =
alias = req.items[2].symName
result.add((libName, alias))
proc compileFile*(inputPath: string, outputPath: string, extraPaths: seq[string] = @[]) =
proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[string] = @[], libMode: bool = false) =
let source = readFile(inputPath)
let forms = reader.readAll(source)
@@ -97,11 +97,17 @@ proc compileFile*(inputPath: string, outputPath: string, extraPaths: seq[string]
f.items[0].kind == ckSymbol and f.items[0].symName == "ns"):
allForms.add(f)
let nimCode = emitter.emitProgram(allForms)
let nimCode = if libMode: emitter.emitProgramLib(allForms) else: emitter.emitProgram(allForms)
writeFile(outputPath, nimCode)
echo "Generated: ", outputPath
proc nimCompile(nimPath: string, binPath: string, release: bool = false): int =
proc compileFile*(inputPath: string, outputPath: string, extraPaths: seq[string] = @[]) =
compileFileInternal(inputPath, outputPath, extraPaths, false)
proc compileFileLib*(inputPath: string, outputPath: string, extraPaths: seq[string] = @[]) =
compileFileInternal(inputPath, outputPath, extraPaths, true)
proc nimCompile(nimPath: string, binPath: string, release: bool = false): tuple[exitCode: int, output: string] =
let libPath = getLibPath()
var cmd = "nim c"
if release:
@@ -109,7 +115,8 @@ proc nimCompile(nimPath: string, binPath: string, release: bool = false): int =
cmd.add(" --path:" & quoteShell(libPath))
cmd.add(" -o:" & quoteShell(binPath) & " " & quoteShell(nimPath))
echo "Compiling: ", cmd
execCmd(cmd)
let (output, exitCode) = execCmdEx(cmd)
return (exitCode, output)
proc makeTempDir(): string =
let pid = getCurrentProcessId()
@@ -143,9 +150,14 @@ proc runFile*(inputPath: string) =
if useCache:
echo "Using cached: ", cacheNimPath
let compileResult = nimCompile(cacheNimPath, cacheBinPath)
let (compileResult, compileOut) = nimCompile(cacheNimPath, cacheBinPath)
if compileResult != 0:
stderr.writeLine("Compilation failed (cached)")
stderr.writeLine(compileOut)
if ai_assist.hasAiConfig():
let source = readFile(inputPath)
let aiRes = ai_assist.explainError(compileOut, source, inputPath)
stderr.writeLine(ai_assist.formatSuggestion(aiRes))
quit(1)
try: removeDir(buildDir) except: discard
let runResult = execCmd(cacheBinPath)
@@ -153,9 +165,14 @@ proc runFile*(inputPath: string) =
compileFile(inputPath, nimPath, depPaths)
let compileResult = nimCompile(nimPath, binPath)
let (compileResult, compileOut) = nimCompile(nimPath, binPath)
if compileResult != 0:
stderr.writeLine("Compilation failed")
stderr.writeLine(compileOut)
if ai_assist.hasAiConfig():
let source = readFile(inputPath)
let aiRes = ai_assist.explainError(compileOut, source, inputPath)
stderr.writeLine(ai_assist.formatSuggestion(aiRes))
try: removeDir(buildDir) except: discard
quit(1)
@@ -188,6 +205,7 @@ proc main() =
echo " cljnim repl [--json] Start REPL (human or AI mode)"
echo " cljnim deps Resolve dependencies from deps.edn"
echo " cljnim -e '<code>' Evaluate expression"
echo " cljnim ai '<description>' Generate Clojure code with AI"
echo ""
echo "REPL Commands:"
echo " :quit, :q Exit REPL"
@@ -215,15 +233,19 @@ proc main() =
let forms = reader.readAll(code)
let nimCode = emitter.emitProgram(forms)
writeFile(nimPath, nimCode)
let compileResult = nimCompile(nimPath, binPath)
let (compileResult, compileOut) = nimCompile(nimPath, binPath)
if compileResult != 0:
stderr.writeLine("Compilation failed")
stderr.writeLine(compileOut)
if ai_assist.hasAiConfig():
let aiRes = ai_assist.explainError(compileOut, code, "-e expression")
stderr.writeLine(ai_assist.formatSuggestion(aiRes))
try: removeDir(buildDir) except: discard
quit(1)
let runResult = execCmd(binPath)
try: removeDir(buildDir) except: discard
quit(runResult)
case cmd
of "compile":
if args.len < 2:
@@ -232,6 +254,14 @@ proc main() =
let inputPath = args[1]
let outputPath = if args.len >= 3: args[2] else: inputPath.changeFileExt("nim")
compileFile(inputPath, outputPath)
of "compile-lib":
if args.len < 2:
stderr.writeLine("Error: Missing input file")
quit(1)
let inputPath = args[1]
let outputPath = if args.len >= 3: args[2] else: inputPath.changeFileExt("nim")
compileFileLib(inputPath, outputPath)
of "run":
if args.len < 2:
@@ -298,6 +328,24 @@ proc main() =
else:
echo "No dependencies to resolve"
of "ai":
if not ai_assist.hasAiConfig():
stderr.writeLine("Error: No AI API key configured.")
stderr.writeLine("Set DEEPSEEK_API_KEY, OPENAI_API_KEY, or MIMO_API_KEY environment variable.")
quit(1)
if args.len < 2:
stderr.writeLine("Error: Missing description for AI generation")
stderr.writeLine("Usage: cljnim ai 'function that reverses a list'")
quit(1)
let description = args[1]
echo "🤖 Asking AI to generate code..."
let aiRes = ai_assist.generateCode(description)
if aiRes.ok:
echo aiRes.suggestion
else:
stderr.writeLine(aiRes.suggestion)
quit(1)
else:
stderr.writeLine("Unknown command: " & cmd)
quit(1)