[Module] tcl_module - embed a TCL intepreter in your application
Posted: Tue Jan 30, 2024 7:11 pm
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
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