feat: add fullscreen TUI and project updates
- New TUI screens: Main Menu, Compile, Run, REPL, AI Generator, AI Settings, Help - AI configuration persisted in ~/.config/cljnim/config.json - Added illwill dependency for terminal UI - Updated experiments, examples, docs, and core modules
This commit is contained in:
+56
-22
@@ -1,9 +1,11 @@
|
||||
# Clojure → Nim Emitter
|
||||
import strutils, sequtils, sets
|
||||
import strutils, sets
|
||||
import types
|
||||
import macros
|
||||
|
||||
var requiredImports* = initHashSet[string]()
|
||||
var emitLibMode* = false
|
||||
var loopStack*: seq[seq[string]] = @[]
|
||||
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
||||
|
||||
proc setNsAliases*(aliases: seq[(string, string)]) =
|
||||
@@ -151,12 +153,6 @@ proc runtimeName(op: string): string =
|
||||
of "close!": "cljChanClose"
|
||||
else: ""
|
||||
|
||||
proc emitArgs(args: seq[CljVal]): string =
|
||||
var parts: seq[string] = @[]
|
||||
for a in args:
|
||||
parts.add(emitExpr(a, 0))
|
||||
return parts.join(", ")
|
||||
|
||||
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let sp = indentStr(indent)
|
||||
let head = items[0]
|
||||
@@ -250,10 +246,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
return form.items[1]
|
||||
# list
|
||||
if hName == "list":
|
||||
var result: seq[CljVal] = @[]
|
||||
var evaluated: seq[CljVal] = @[]
|
||||
for i in 1..<form.items.len:
|
||||
result.add(eval(form.items[i]))
|
||||
return cljList(result)
|
||||
evaluated.add(eval(form.items[i]))
|
||||
return cljList(evaluated)
|
||||
# cons
|
||||
if hName == "cons" and form.items.len == 3:
|
||||
let fst = eval(form.items[1])
|
||||
@@ -346,7 +342,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
# if/when branches return their last expression properly.
|
||||
bodyCode = bodyCode.replace("discard ", "result = ")
|
||||
let procName = mangleName(name.symName)
|
||||
return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||
let exportMarker = if emitLibMode: "*" else: ""
|
||||
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||
|
||||
of "defn-":
|
||||
if items.len < 4:
|
||||
@@ -433,7 +430,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if not (thenStripped.startsWith("echo ") or thenStripped.startsWith("discard ") or
|
||||
thenStripped.startsWith("block:") or thenStripped.startsWith("if ") or
|
||||
thenStripped.startsWith("try:") or thenStripped.startsWith("var ") or
|
||||
thenStripped.startsWith("let ") or thenStripped.startsWith("proc ")):
|
||||
thenStripped.startsWith("let ") or thenStripped.startsWith("proc ") or
|
||||
thenStripped.contains("\n") or thenStripped.contains(" = ")):
|
||||
thenFinal = indentStr(indent + 1) & "discard " & thenStripped
|
||||
var ifBlock = sp & "if cljIsTruthy(" & condCode & "):\n" & thenFinal
|
||||
if items.len == 4:
|
||||
@@ -443,7 +441,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if not (elseStripped.startsWith("echo ") or elseStripped.startsWith("discard ") or
|
||||
elseStripped.startsWith("block:") or elseStripped.startsWith("if ") or
|
||||
elseStripped.startsWith("try:") or elseStripped.startsWith("var ") or
|
||||
elseStripped.startsWith("let ") or elseStripped.startsWith("proc ")):
|
||||
elseStripped.startsWith("let ") or elseStripped.startsWith("proc ") or
|
||||
elseStripped.contains("\n") or elseStripped.contains(" = ")):
|
||||
elseFinal = indentStr(indent + 1) & "discard " & elseStripped
|
||||
ifBlock.add("\n" & sp & "else:\n" & elseFinal)
|
||||
return ifBlock
|
||||
@@ -540,22 +539,32 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
raise newException(EmitterError, "loop binding name must be a symbol")
|
||||
loopParams.add((mangleName(lname.symName), emitExpr(lval, 0)))
|
||||
li += 2
|
||||
var loopVars: seq[string] = @[]
|
||||
var lines: seq[string] = @[]
|
||||
for (lpName, lpVal) in loopParams:
|
||||
lines.add(sp & "var " & lpName & ": CljVal = " & lpVal)
|
||||
loopVars.add(lpName)
|
||||
loopStack.add(loopVars)
|
||||
lines.add(sp & "while true:")
|
||||
let body = items[2..^1]
|
||||
for b in body:
|
||||
lines.add(emitExpr(b, indent + 1))
|
||||
lines.add(indentStr(indent + 1) & "break")
|
||||
discard loopStack.pop()
|
||||
return lines.join("\n")
|
||||
|
||||
of "recur":
|
||||
if items.len < 2:
|
||||
raise newException(EmitterError, "recur requires arguments")
|
||||
# Simplified: emit as discard (real impl needs loop var assignment)
|
||||
if loopStack.len == 0:
|
||||
raise newException(EmitterError, "recur outside of loop")
|
||||
let loopVars = loopStack[^1]
|
||||
if items.len - 1 != loopVars.len:
|
||||
raise newException(EmitterError, "recur requires " & $loopVars.len & " arguments, got " & $(items.len - 1))
|
||||
var lines: seq[string] = @[]
|
||||
for ri in 1..<items.len:
|
||||
lines.add(sp & "discard " & emitExpr(items[ri], 0))
|
||||
lines.add(sp & loopVars[ri-1] & " = " & emitExpr(items[ri], 0))
|
||||
lines.add(sp & "continue")
|
||||
return lines.join("\n")
|
||||
|
||||
of "do":
|
||||
@@ -567,7 +576,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if items.len < 2:
|
||||
raise newException(EmitterError, "try requires body")
|
||||
var bodyForms: seq[CljVal] = @[]
|
||||
var catchClauses: seq[(string, seq[CljVal])] = @[]
|
||||
var catchClauses: seq[(string, string, seq[CljVal])] = @[]
|
||||
var finallyBody: seq[CljVal] = @[]
|
||||
var ti = 1
|
||||
while ti < items.len:
|
||||
@@ -576,10 +585,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if form.items[0].symName == "catch":
|
||||
if form.items.len < 3:
|
||||
raise newException(EmitterError, "catch requires exception type, name, and body")
|
||||
let exType = form.items[1]
|
||||
var exTypeStr = "CatchableError"
|
||||
if exType.kind == ckSymbol:
|
||||
exTypeStr = exType.symName
|
||||
let exName = form.items[2]
|
||||
if exName.kind != ckSymbol:
|
||||
raise newException(EmitterError, "catch name must be a symbol")
|
||||
catchClauses.add((mangleName(exName.symName), form.items[3..^1]))
|
||||
catchClauses.add((exTypeStr, mangleName(exName.symName), form.items[3..^1]))
|
||||
elif form.items[0].symName == "finally":
|
||||
finallyBody = form.items[1..^1]
|
||||
else:
|
||||
@@ -591,9 +604,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
lines.add(sp & "try:")
|
||||
for b in bodyForms:
|
||||
lines.add(emitExpr(b, indent + 1))
|
||||
for (exVar, exBody) in catchClauses:
|
||||
lines.add(sp & "except CatchableError:")
|
||||
lines.add(indentStr(indent + 1) & "let " & exVar & " = cljString(getCurrentExceptionMsg())")
|
||||
for (exType, exVar, exBody) in catchClauses:
|
||||
lines.add(sp & "except " & exType & ":")
|
||||
lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])")
|
||||
lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))")
|
||||
for eb in exBody:
|
||||
lines.add(emitExpr(eb, indent + 1))
|
||||
if finallyBody.len > 0:
|
||||
@@ -889,7 +903,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
for i in 1..<items.len:
|
||||
argParts.add(emitExpr(items[i], 0))
|
||||
# Known Nim module interop patterns
|
||||
let fullFn = module & "." & funcChain
|
||||
case module
|
||||
of "math":
|
||||
requiredImports.incl("math")
|
||||
@@ -1029,6 +1042,19 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
keyParts.add(emitExpr(v.mapKeys[i], 0))
|
||||
valParts.add(emitExpr(v.mapVals[i], 0))
|
||||
return sp & "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])"
|
||||
of ckSet:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.setItems:
|
||||
parts.add(emitExpr(item, 0))
|
||||
return sp & "cljSet(@[" & parts.join(", ") & "])"
|
||||
of ckFn:
|
||||
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = discard cljNil())"
|
||||
of ckAtom:
|
||||
return sp & "cljAtom(" & emitExpr(v.atomVal, 0) & ")"
|
||||
of ckTransient:
|
||||
return sp & "cljTransient(cljNil())"
|
||||
of ckAgent:
|
||||
return sp & "cljAgent(" & emitExpr(v.agentVal, 0) & ")"
|
||||
|
||||
proc emitBlock(items: seq[CljVal], indent: int): string =
|
||||
if items.len == 0:
|
||||
@@ -1038,7 +1064,7 @@ proc emitBlock(items: seq[CljVal], indent: int): string =
|
||||
lines.add(emitExpr(item, indent))
|
||||
return lines.join("\n")
|
||||
|
||||
proc emitProgram*(forms: seq[CljVal]): string =
|
||||
proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
requiredImports = initHashSet[string]()
|
||||
var headerLines: seq[string] = @[
|
||||
"# Generated by Clojure/Nim",
|
||||
@@ -1080,3 +1106,11 @@ proc emitProgram*(forms: seq[CljVal]): string =
|
||||
lines.add(form)
|
||||
|
||||
return lines.join("\n") & "\n"
|
||||
|
||||
proc emitProgram*(forms: seq[CljVal]): string =
|
||||
emitLibMode = false
|
||||
emitProgramInternal(forms)
|
||||
|
||||
proc emitProgramLib*(forms: seq[CljVal]): string =
|
||||
emitLibMode = true
|
||||
emitProgramInternal(forms)
|
||||
|
||||
Reference in New Issue
Block a user