Search code examples
nim-lang

How to catch the output of a compiler error in nim?


I am not sure if this is currently possible (and maybe it is not even advisable), but I would like to be able to catch the output of a compiler error and reuse it in code. An example would be:

type IntOrString = int | string
var s: seq[IntOrString]

this code would not compile with error:

/usercode/in.nim(2, 5) Error: invalid type: 'IntOrString' in this context: 'seq[IntOrString]' for var

I am interested in a way to be able to use the message of this error in the code.

My use case is being able to easily document and discuss compiler errors in nimib. If I were to write a document that shows and discuss the different type of compiler errors catching the message automatically that could be useful (a workaround right now would be to write the code to file and compile with verbosity 0).


Solution

  • It is possible to use the compiler api to catch errors:

    import ../compiler/[nimeval, llstream, ast, lineinfos, options], os, strformat
    
    let std = findNimStdLibCompileTime()
    let modules = [std, std / "pure", std / "std", std / "core"]
    var intr = createInterpreter("script", modules)
    intr.registerErrorHook proc(config:ConfigRef,info:TLineInfo,msg:string,severity:Severity) =
      raise newException(CatchableError,&"{severity}: {(info.line,info.col)}  {msg}")
    )
    
    
    
    try:
      intr.evalScript(llStreamOpen("""type IntOrString = int | string
    var s: seq[IntOrString]"""))
    
    except CatchableError as e:
      echo e.msg
    echo "done"
    destroyInterpreter(intr)
    

    outputs:

    Error: (2, 4) invalid type: 'IntOrString' in this context: 'seq[IntOrS
    tring]' for var
    done
    

    Caveat: you can't run runtime code at compile time, for example, trying to run

    type Dog = object of RootObj
    method speak(i:Dog):string = "woof"
    echo Dog().speak()
    

    in the interpreter will give you errors, instead you would have to do something like:

    type Dog = object of RootObj
    method speak*(i:Dog):string = "woof"
    proc newDog*():Dog = discard
    
    let 
      dog = intr.callRoutine(intr.selectRoutine("newDog"),[])
      speech = intr.callRoutine(intr.selectRoutine("speak"),[dog])
    if speech.kind == nkStrLit:
      echo speech.strval