[Module] tcl_module - embed a TCL intepreter in your application

Applications, Games, Tools, User libs and useful stuff coded in PureBasic
pjsmith67
User
User
Posts: 48
Joined: Thu Apr 26, 2018 3:09 pm

[Module] tcl_module - embed a TCL intepreter in your application

Post by pjsmith67 »

A simple module for embedding TCL scripts into your application.
Not all features are implemented, but should be enough to get you started.
Example usage included. Enjoy.

Phil

Code: Select all

;TCL Module V0.1 - Phil Smith
;not all TCL API commands are implemented, not even close to all of them
;not all declarations are used... yet.

;to add a TCL API function
;   1.  Create the prototype
;   2.  Create a global variable of that prototype
;   3.  add a call to assignfunction in tcl_init to map the variable to the tcl API function.
;TCL functions are case sensitive!!!!
DeclareModule tcl
  
  #tcl_ok=0
  #tcl_error=1
  
  #tcl_volatile=1
  #tcl_static=0
  #tcl_dynamic=3
  
  #TCL_GLOBAL_ONLY=           1
  #TCL_NAMESPACE_ONLY=        2
  #TCL_APPEND_VALUE=          4
  #TCL_LIST_ELEMENT=          8
  #TCL_TRACE_READS=           $10
  #TCL_TRACE_WRITES=          $20
  #TCL_TRACE_UNSETS=          $40
  #TCL_TRACE_DESTROYED=       $80
  #TCL_INTERP_DESTROYED=      $100
  #TCL_LEAVE_ERR_MSG=         $200
  #TCL_TRACE_ARRAY=           $800
  
  #TCL_READABLE =           (1<<1)
  #TCL_WRITABLE=            (1<<2)
  #TCL_EXCEPTION=           (1<<3)
  
  ; /*
  ;  * Flag values To pass To Tcl_OpenCommandChannel To indicate the disposition
  ;  * of the stdio handles. TCL_STDIN, TCL_STDOUT, TCL_STDERR, are also used in
  ;  * Tcl_GetStdChannel.
  ;  */
  
  #TCL_STDIN=               (1<<1)
  #TCL_STDOUT=              (1<<2)
  #TCL_STDERR=              (1<<3)
  #TCL_ENFORCE_MODE=        (1<<4)
  
  ImportC ""
    dup(fileID)
    close(fileID)
  EndImport  
  
  Structure twoPtrValue
    *ptr1
    *ptr2
  EndStructure
  
  Structure TptrAndLongRep
    *ptr
    value.l
  EndStructure
  
  Structure Tcl_Obj
    refCount.i
    *bytes.string
    length.i
    *typePtr
    StructureUnion
      intValue.i
      doubleValue.d
      *otherValuePtr
      prtValue.twoPtrValue
      ptrAndLongRep.TptrAndLongRep
    EndStructureUnion
  EndStructure
  
  Prototype.i proto_tcl_createInterp() 
  Prototype.i proto_tcl_deleteInterp(*interp)
  Prototype.i proto_tcl_ObjCmdProc(*dummy,*interp,objc,*objv)
  Prototype.i proto_tcl_createobjCommand(*interp,cmdName.p-utf8,*proc,*clientData,*deleteProc)
  Prototype.i proto_tcl_SetObjResult(*interp,*objPtr)
  Prototype.i proto_tcl_Eval(*interp,script.p-utf8)
  Prototype.i proto_tcl_EvalFie(*interp,filename.p-utf8)
  Prototype.i proto_tcl_FSGetCwd(*ptr)
  Prototype.i proto_tcl_FSChdir(*ptr,path.p-utf8)
  Prototype.i proto_tcl_SetStringObj(*ptr,bytes.p-utf8,length)
  Prototype.i proto_tcl_NewStringObj(bytes.p-utf8,length)
  Prototype.i proto_tcl_GetStringResult(*interp)
  Prototype.i proto_tcl_GetObjResult(*interp)
  Prototype.i proto_tcl_GetStringFromObj(*ptr,*lengthPtr)
  Prototype.i proto_tcl_GetString(*obj)
  Prototype.i proto_tcl_GetVar2Ex(*interp,name1.p-utf8,name2.p-utf8,flags.i)
  Prototype.i proto_tcl_GetReturnOptions(*interp,code.i)
  Prototype.i proto_tcl_GetVar(*interp,varName.p-utf8,flags)
  Prototype.i proto_tcl_DictObjGet(*interp,*error_dict,*error_info,*error_msg)
  Prototype.i proto_tcl_MakeFileChannel(*fp,flags)
  Prototype.i proto_tcl_RegisterChannel(*ptr,*channel)
  Prototype.i proto_tcl_SetStdChannel(*channel,channel_id)
  Prototype.i proto_tcl_Close(*interp,*channel)
  Prototype.i proto_tcl_GetChannelName(*channel)
  Prototype.i proto_tcl_OpenFileChannel(*interp,fileName.p-utf8,mode.p-ascii,permissions)
  Prototype.i proto_tcl_SetChannelOption(*interp,*channel,optionName.p-utf8,optionValue.p-utf8)
  Prototype.i proto_tcl_UnregisterChannel(*interp,*channel)
  Prototype.i proto_tcl_GetStdChannel(channel_id)
  Prototype.i proto_tcl_SetVar(*interp,varName.p-utf8,newValue.p-UTF8,flags)
  Prototype.i proto_tcl_Free(*ptr)
  Prototype.i proto_tcl_FreeResult(*ptr)
  Prototype.i proto_tcl_ResetResult(*ptr)
  
  
  Global tcl_Close.proto_tcl_Close
  Global tcl_createInterp.proto_tcl_createInterp
  Global tcl_createObjCommand.proto_tcl_createobjCommand
  Global tcl_DictObjGet.proto_tcl_DictObjGet
  Global tcl_DeleteInterp.proto_tcl_DeleteInterp
  Global tcl_eval.proto_tcl_Eval
  Global tcl_evalFile.proto_tcl_EvalFie
  Global tcl_FSGetCwd.proto_tcl_FSGetCwd
  Global tcl_FSChdir.proto_tcl_FSChdir
  Global tcl_GetChannelName.proto_tcl_GetChannelName
  Global tcl_GetObjResult.proto_tcl_GetObjResult
  Global tcl_GetReturnOptions.proto_tcl_GetReturnOptions
  Global tcl_GetStdChannel.proto_tcl_GetStdChannel
  Global tcl_GetString.proto_tcl_GetString
  Global tcl_GetStringFromObj.proto_tcl_GetStringFromObj
  Global tcl_GetStringResult.proto_tcl_GetStringResult
  Global tcl_GetVar.proto_tcl_GetVar
  Global tcl_GetVar2Ex.proto_tcl_GetVar2Ex
  Global tcl_MakeFileChannel.proto_tcl_MakeFileChannel
  Global tcl_NewStringObj.proto_tcl_NewstringObj
  Global tcl_OpenFileChannel.proto_tcl_OpenFileChannel
  Global tcl_RegisterChannel.proto_tcl_RegisterChannel
  Global tcl_SetChannelOption.proto_tcl_SetChannelOption
  Global tcl_SetObjResult.proto_tcl_SetObjResult
  Global tcl_SetStdChannel.proto_tcl_SetStdChannel
  Global tcl_UnregisterChannel.proto_tcl_UnregisterChannel
  Global tcl_setVar.proto_tcl_SetVar
  Global tcl_Free.proto_tcl_Free
  Global tcl_FreeResult.proto_tcl_FreeResult
  Global tcl_ResetResult.proto_tcl_ResetResult
  
  #TCL_LIBRARY_NAME="libtcl.dylib" ;default TCL library, you can change it here or when you call tcl_init
  
  Define tclError$
  Declare tcl_init(*msg.string,lib$=#TCL_LIBRARY_NAME)
  Declare.s tcl_stackTrace(*interp,rc)
  Declare.s tcl_getResult(*interp)
  Declare tcl_setResult(*interp,r$) ;old tcl function wrapped to use new API
  
  
  
EndDeclareModule

Module tcl
  EnableExplicit
  
  Define *interp
  
  Prototype proto_handleError(msg$)
  
  Macro assignFunction(funcVar,funcName)
    funcVar=GetFunction(tclLib,funcName)
    If funcVar=#nil
      s$=funcName+" is null"
      r=#False
    EndIf
  EndMacro
  
  ;you must call this before using TCL.  Returns false with error message in *msg if fails
  ;*msg.string - pointer to a message buffer
  ;lib$ - your tcl library
  Procedure tcl_init(*msg.string,lib$=#TCL_LIBRARY_NAME)  
    ;    tcl_shareFunctions()
    Define tclLib
    Define s$
    If Not IsLibrary(tclLib)
      tclLib= OpenLibrary(#PB_Any,lib$)
      If Not tclLib
        s$="tcl library not found"
        PokeS(*msg,s$)
        ProcedureReturn #False
      EndIf
    EndIf
    Define  r=#True
    assignFunction(tcl_createInterp,"Tcl_CreateInterp")
    assignFunction(tcl_createObjCommand,"Tcl_CreateObjCommand")
    assignFunction(tcl_SetObjResult,"Tcl_SetObjResult")
    ; assignFunction(tcl_SetResult,"Tcl_SetResult")
    assignFunction(tcl_eval,"Tcl_Eval")
    assignFunction(tcl_NewStringObj,"Tcl_NewStringObj")
    assignFunction(tcl_GetStringResult,"Tcl_GetStringResult")
    assignFunction(tcl_GetObjResult,"Tcl_GetObjResult")
    assignFunction(tcl_GetStringFromObj,"Tcl_GetStringFromObj")
    ;assignFunction(tcl_GetErrorLine,"Tcl_GetErrorLine")
    assignFunction(tcl_GetVar2Ex,"Tcl_GetVar2Ex")
    assignFunction(tcl_GetString,"Tcl_GetString")
    assignFunction(tcl_GetReturnOptions,"Tcl_GetReturnOptions")
    assignFunction(tcl_GetVar,"Tcl_GetVar")
    assignFunction(tcl_DictObjGet,"Tcl_DictObjGet")
    assignFunction(tcl_MakeFileChannel,"Tcl_MakeFileChannel")
    assignFunction(tcl_RegisterChannel,"Tcl_RegisterChannel")
    assignFunction(tcl_SetStdChannel,"Tcl_SetStdChannel")
    assignFunction(tcl_Close,"Tcl_Close")
    assignFunction(tcl_GetChannelName,"Tcl_GetChannelName")
    assignFunction(tcl_OpenFileChannel,"Tcl_OpenFileChannel")
    assignFunction(tcl_SetChannelOption,"Tcl_SetChannelOption")
    assignFunction(tcl_UnregisterChannel,"Tcl_UnregisterChannel")
    assignFunction(tcl_GetStdChannel,"Tcl_GetStdChannel")
    assignfunction(tcl_DeleteInterp,"Tcl_DeleteInterp")
    assignFunction(tcl_evalFile,"Tcl_EvalFile")
    assignFunction(tcl_FSGetCwd,"Tcl_FSGetCwd")
    assignFunction(tcl_FSChdir,"Tcl_FSChdir")
    assignFunction(tcl_SetVar,"Tcl_SetVar")
    assignFunction(tcl_GetVar,"Tcl_GetVar")
    assignfunction(tcl_Free,"Tcl_Free")
    assignfunction(tcl_FreeResult,"Tcl_FreeResult")
    assignfunction(tcl_ResetResult,"Tcl_ResetResult")
    
    If s$<>""
      PokeS(*msg,s$)
      
    EndIf
    
    ProcedureReturn r
    
  EndProcedure
  
  ;get the stack trace for an error
  Procedure.s tcl_stackTrace(*interp,rc)
    Define *options=tcl_GetReturnOptions(*interp,rc)
    Define *key=tcl_NewStringObj("-errorinfo",-1)
    Define *stacktrace.Tcl_Obj=AllocateStructure(Tcl_Obj)
    Tcl_DictObjGet(*interp,*options,*key,@*stacktrace)
    ProcedureReturn PeekS(*stacktrace\bytes,-1,#PB_UTF8)
  EndProcedure
  
  ;get the result of a TCL command execution
  Procedure.s tcl_getResult(*interp)
    Define r$
    Define *obj.Tcl_Obj=tcl_GetObjResult(*interp)
    r$=PeekS(*obj\bytes,-1,#PB_UTF8)
    ProcedureReturn r$
  EndProcedure
  
  Procedure tcl_setResult(*interp,r$)
    Define *obj=tcl::tcl_NewStringObj(r$,-1)
    tcl_setObjResult(*interp,*obj)
  EndProcedure
  
EndModule

CompilerIf #PB_Compiler_IsMainFile
  UseModule tcl
  
  
  Macro quotestr(s)
    Chr(34)+s+Chr(34)
  EndMacro
  
  ;get all the arguments passed to a custom tcl function and put them in params$()
  ;the first element in the list will be the name of the command
  Procedure getParameters(objc,*objv.integer,List params$())
    Define cnt
    Define *ptr.Tcl_Obj
    Define s$
    Shared *app
    ;*objv+SizeOf(integer)
    For cnt=0 To objc-1
      *ptr=*objv\i
      If *ptr<>#nil And *ptr\bytes<>#nil
        s$=PeekS(*ptr\bytes,-1,#PB_UTF8)
        AddElement(params$())
        params$()=s$
        *objv+SizeOf(integer)
      EndIf
      
    Next
  EndProcedure
  
  ;a custom TCL command
  ;*dummy - ignore
  ;*interp - pointer to tcl interpreter
  ;objc - number of arguments, including the command itself
  ;objv - pointer to list of arguments
  ;       Use getParameters to parse this
  Procedure tclDebug(*dummy,*interp,objc,*objv.integer)
    Define cnt
    Define *ptr.Tcl_Obj
    Define s$
    NewList params$()
    getParameters(objc,*objv,params$())
    ForEach(params$())
      If ListIndex(params$())=0
        Continue ;skip first element since its just the command name
      EndIf
      s$=s$+" "+params$()
    Next
    
    Debug s$
  EndProcedure
  
  Define msg$=Space(200)
  If Not tcl_init(@msg$)
    Debug msg$  ;most likely a function was not successully mapped to a variable due to misspelling
    End
  EndIf
  
  
  Define *interp=tcl_CreateInterp()
  
  ;register our custom TCL command
  tcl_createObjCommand(*interp,"debug",@tclDebug(),0,0)
  
  Restore testcode
  Define line$
  Read.s line$
  Define code$
  While line$<>""
    code$+line$+#LF$
    Read.s line$
  Wend
  Define rc=tcl_eval(*interp,code$)
  If rc<>#TCL_OK
    Debug tcl_stackTrace(*interp,rc)
  Else
    Debug "execution completed"
    Debug "result="+tcl_getResult(*interp)
  EndIf
  tcl_DeleteInterp(*interp)
    
  DataSection
    testcode:
    Data$ "proc hello {} {"
    Data$ "debug hello"
    Data$ "}"
    Data$ "hello"
    Data$ "set msg "+quotestr("hello again")
    Data$ "debug $msg"
    Data$ ""
  EndDataSection
  
CompilerEndIf
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by Kwai chang caine »

Hello pjsmith67

Thanks for sharing 8)
The constante #nil not exist :|
ImageThe happiness is a road...
Not a destination
pjsmith67
User
User
Posts: 48
Joined: Thu Apr 26, 2018 3:09 pm

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by pjsmith67 »

Hmmm... that's weird, it is a standard PB constant, at least with my version, 6.04.
User avatar
CDXbow
Enthusiast
Enthusiast
Posts: 100
Joined: Mon Aug 12, 2019 5:32 am
Location: Oz

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by CDXbow »

Kwai chang caine wrote: Tue Jan 30, 2024 9:05 pm Hello pjsmith67

Thanks for sharing 8)
The constante #nil not exist :|
I'm getting the same error on Win10 64bit PB 6.03
User avatar
Caronte3D
Addict
Addict
Posts: 1361
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by Caronte3D »

Maybe the constant is #NUL? :?

Or... it's only for Linux?
pjsmith67
User
User
Posts: 48
Joined: Thu Apr 26, 2018 3:09 pm

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by pjsmith67 »

I develop on a Mac. So that's a possibility, I guess.

Easy enough fix though, just add your own definition.

#nil=0

Never claimed I write perfect code. :-D

Phil
loulou2522
Enthusiast
Enthusiast
Posts: 550
Joined: Tue Oct 14, 2014 12:09 pm

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by loulou2522 »

What is a tcl script ?
Thanks for answering
User avatar
useful
Enthusiast
Enthusiast
Posts: 402
Joined: Fri Jul 19, 2013 7:36 am

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by useful »

Dawn will come inevitably.
User avatar
idle
Always Here
Always Here
Posts: 5896
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by idle »

Thanks this is one to remember :D
DarkDragon
Addict
Addict
Posts: 2345
Joined: Mon Jun 02, 2003 9:16 am
Location: Germany
Contact:

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by DarkDragon »

loulou2522 wrote: Thu Feb 01, 2024 8:40 am What is a tcl script ?
Thanks for answering
Everything is a string. That's TCL in one sentence 🤣. Even lists, maps, ... everything is a string! It's a scripting language, but IMHO a bit weird, buggy and old.

https://www.tcl-lang.org/

I constantly have to deal with hanging tclsh instances. But it's interesting to see this here.
bye,
Daniel
pjsmith67
User
User
Posts: 48
Joined: Thu Apr 26, 2018 3:09 pm

Re: [Module] tcl_module - embed a TCL intepreter in your application

Post by pjsmith67 »

loulou2522 wrote: Thu Feb 01, 2024 8:40 am What is a tcl script ?
Thanks for answering
Its not the most popular scripting language and while still supported, development seems to be kinda slow.

However, I like it, used a lot many years ago, and it was fairly easy to embed it into Purebasic and even easier to add custom commands to it.
Post Reply