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:
+57
-9
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user