├── .gitignore ├── package ├── example.tcl ├── test.tcl ├── impork.tcl ├── README.md └── scaffolder.tcl ├── XCODE.md ├── Package.swift ├── Makefile ├── INSTALL.md ├── Sources ├── tcl-array.swift ├── tcl.swift ├── tcl-interp.swift └── tcl-object.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .build/* 2 | Package.pins 3 | SwiftTcl.xcodeproj/* 4 | -------------------------------------------------------------------------------- /package/example.tcl: -------------------------------------------------------------------------------- 1 | # 2 | # Apply the scaffolder to itself to test its output 3 | # 4 | source scaffolder.tcl 5 | 6 | foreach proc [::swift::enumerate_procs ::swift] { 7 | puts [::swift::gen $proc] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /package/test.tcl: -------------------------------------------------------------------------------- 1 | # 2 | # Test code for swift-tcl scaffold generator 3 | # 4 | if [file exists scaffolder.tcl] { 5 | source scaffolder.tcl 6 | proc hint {args} { 7 | uplevel 1 ::swift::hint {*}$args 8 | } 9 | } else { 10 | proc hint {args} {} 11 | } 12 | 13 | source impork.tcl 14 | 15 | puts [::swift::gen impork] 16 | 17 | -------------------------------------------------------------------------------- /XCODE.md: -------------------------------------------------------------------------------- 1 | ## Setting up for XCODE development 2 | 3 | 1. `make SwiftTcl.xcodeproj` 4 | 2. Manually edit the SwiftTclDemo scheme to manually add the root directory of this repo: 5 | 6 | ``` 7 | SwiftTclDemo > Edit Scheme ... > Run > Working Directory 8 | [x] Use Custom Working directory: 9 | [select the current directory here] 10 | ``` 11 | -------------------------------------------------------------------------------- /package/impork.tcl: -------------------------------------------------------------------------------- 1 | 2 | proc impork {file {first 1} {step 1}} { 3 | if [catch {open $file} status] { 4 | return {} 5 | } 6 | set fp $status 7 | set contents [read $fp] 8 | close $fp 9 | set result {} 10 | set ln $first 11 | foreach line [split $contents "\n"] { 12 | lappend result $ln $line 13 | incr ln $step 14 | } 15 | return $result 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:3.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftTcl", 7 | dependencies: [ 8 | .Package(url: "https://github.com/flightaware/swift-tcl8.6.git", majorVersion: 1) 9 | ] 10 | ) 11 | 12 | let libSwiftTcl = Product(name: "SwiftTcl", type: .Library(.Dynamic), modules: "SwiftTcl") 13 | products.append(libSwiftTcl) 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNAME_S := $(shell uname -s) 2 | 3 | ifeq ($(UNAME_S),Linux) 4 | EXTRA_SWIFTLINK= 5 | TARGET = .build/debug/libSwiftTcl.so 6 | endif 7 | 8 | ifeq ($(UNAME_S),Darwin) 9 | TCLVERSION=8.6.6_2 10 | BREWROOT=/usr/local/Cellar 11 | TCLLIBPATH=$(BREWROOT)/tcl-tk/$(TCLVERSION)/lib 12 | TCLINCPATH=$(BREWROOT)/tcl-tk/$(TCLVERSION)/include 13 | EXTRA_SWIFTLINK=-Xlinker -L/usr/local/lib \ 14 | -Xlinker -L$(TCLLIBPATH) \ 15 | -Xcc -I$(TCLINCPATH) 16 | TARGET = .build/debug/libSwiftTcl.dylib 17 | PROJECT = SwiftTcl.xcodeproj 18 | endif 19 | 20 | default: $(TARGET) 21 | 22 | $(TARGET): Package.swift Makefile 23 | swift build $(EXTRA_SWIFTLINK) 24 | 25 | project: $(PROJECT) 26 | 27 | $(PROJECT): Package.swift Makefile $(TARGET) 28 | swift package $(EXTRA_SWIFTLINK) generate-xcodeproj 29 | @echo "NOTE: You will need to manually set the working directory for the SwiftTclDemo scheme to the root directory of this tree." 30 | @echo "Thanks Apple" 31 | 32 | ifeq ($(UNAME_S),Linux) 33 | install: $(TARGET) 34 | cp $(TARGET) /usr/lib/x86_64-linux-gnu 35 | ldconfig /usr/lib/x86_64-linux-gnu/libSwiftTcl.so 36 | endif 37 | 38 | ifeq ($(UNAME_S),Darwin) 39 | install: $(TARGET) 40 | cp $(TARGET) /usr/local/lib 41 | endif 42 | 43 | clean: 44 | rm -rf $(TARGET) .build Package.pins 45 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ### Build on Linux 2 | 3 | cd 4 | swift build -Xlinker -ltcl8.6 5 | 6 | ### Build on OS X with Brew 7 | 8 | #### Brew Install 9 | Install tcl with brew. Get version 8.6+. 10 | 11 | ``` 12 | brew install tcl-tk 13 | ``` 14 | 15 | Brew creates pkg-config files, which Swift package manager attempts to use. But in this case we are still not getting the correct flags for the compiler or linker. Find your brew package config file /usr/local/Cellar/tcl-tk/8.6.6_2/lib/pkgconfig/tcl.pc. Use that to get the link and include values to pass in. 16 | ``` 17 | swift build -Xlinker -L/usr/local/Cellar/tcl-tk/8.6.6_2/lib -Xlinker -ltcl8.6 -Xlinker -ltclstub8.6 -Xlinker -lz -Xcc -I/usr/local/Cellar/tcl-tk/8.6.6_2/include 18 | ``` 19 | 20 | #### Update the Tcl Framework 21 | 22 | Install brew ... then reinstall from the Tcl source. Brew provides the tcl8.6 needed by the installer. You figure out other ways to get tclsh8.6 for the installer. 23 | 24 | This allows one to build with these minimal flags for linking. 25 | ``` 26 | swift build -Xlinker -lTcl 27 | ``` 28 | And in Xcode you only need to add "Link/Other Libraries" as -lTcl 29 | 30 | THIS WILL REMOVE YOUR Apple Tcl BUILDS!!! 31 | ``` 32 | cd tcl8.6.6/macox 33 | ./configure --enable-framework 34 | make 35 | sudo make -e NATIVE_TCLSH=/usr/local/bin/tclsh8.6 install 36 | ``` 37 | 38 | If you need to restore for Tcl8.4 and Tcl8.5 Frameworks from Apple they are in 39 | ``` 40 | /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Tcl.framework 41 | ``` 42 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | In the packages directory is scaffolder.tcl. It has code to marshall all the procs (swift::enumerate_procs). swift::gen generates a Swift function definition for the proc by examining its arguments and default values. 4 | 5 | For a given proc name, swift::gen attempts to infer the data types of the arguments by examining their defaults and emits a Swift function declaration that will invoke the Tcl springboard so that the Tcl proc is a native function in Swift. 6 | 7 | It can recognize Int, Double, Bool and String. 8 | 9 | There are a few problems, though… 10 | 11 | * Tcl developers tend to use 0 for default values for booleans so we guess Int instead of Bool. 12 | * If a function argument doesn’t take a default value then we just say it’s String. 13 | * We can pretty much see if the proc returns something but we only guess that it’s a String. 14 | 15 | Consequently there is a hinting system. Right now you can say “swift::hint photos_display_hot_rating starWidth Int -> String” 16 | 17 | What it takes as an argument is the proc name and then key-value pairs for the overrides for any proc arguments. The special string “->” defines the return type. 18 | 19 | I think these hints ultimately belong in comments in the source code. 20 | 21 | I have been toying with that possibly not every proc needs to be directly accessible from Swift (but maybe it would be best if it did?). I am also toying with the idea that you might bite the bullet and include a full Swift declaration for the proc inside the comments. 22 | 23 | ```tcl 24 | proc flightaware_photos_displayWidget {compare sort {limit 6} {context default} {style default} {photos_period 0} {dryrun 0}} {...} 25 | ``` 26 | 27 | 28 | ``` 29 | % swift::gen flightaware_photos_displayWidget 30 | func flightaware_photos_displayWidget (compare: String, sort: String, limit: Int = 6, context: String = "default", style: String = "default", photos_period: Int = 0, dryrun: Int = 0 -> String) { 31 | return tcl_springboard(springboardInterp, "flightaware_photos_displayWidget", string_to_tclobjp(compare), string_to_tclobjp(sort), Tcl_NewLongObj(limit), string_to_tclobjp(context), string_to_tclobjp(style), Tcl_NewLongObj(photos_period), Tcl_NewLongObj(dryrun)) 32 | } 33 | ``` 34 | 35 | In the above example *dryrun* should have been a Bool, consequently we tell the hinter... ``` % swift::hint flightaware_photos_displayWidget dryrun Bool ``` to produce 36 | 37 | ```tcl 38 | func flightaware_photos_displayWidget (compare: String, sort: String, limit: Int = 6, context: String = "default", style: String = "default", photos_period: Int = 0, dryrun: Bool = 0 -> String) { 39 | return tcl_springboard(springboardInterp, "flightaware_photos_displayWidget", string_to_tclobjp(compare), string_to_tclobjp(sort), Tcl_NewLongObj(limit), string_to_tclobjp(context), string_to_tclobjp(style), Tcl_NewLongObj(photos_period), Tcl_NewBooleanObj(dryrun ? 1 : 0)) 40 | } 41 | ``` 42 | 43 | A tricky issue is going to be passing Swift objects around in Tcl. I think it will be possible to accept objects possibly as the AnyObject type and get the object type as a string from Swift. Definitely there is a way to get the unsafeAddressOf something so an instance of an object that in Swift may not have any kind of name can have a name in Tcl consisting of the object type concatenated with the address of the object as a string, a lot like Swig does for bringing C and C++ stuff to Tcl in an automated way. 44 | -------------------------------------------------------------------------------- /package/scaffolder.tcl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | namespace eval ::swift { 6 | variable hints 7 | 8 | # hint 9 | # 10 | # swift::hint photos_display_hot_rating starWidth Int -> String 11 | proc hint {proc args} { 12 | variable hints 13 | 14 | set hints($proc) $args 15 | } 16 | 17 | # 18 | # guess_default_type - given a string try to suss out what 19 | # data type it is. first see if it is a strict int, then 20 | # see if it is a strict double, then see if it is a 21 | # string boolean. If it wasn't one of those then say it is a string. 22 | # 23 | proc guess_default_type {defaultValue} { 24 | if {[string is int -strict $defaultValue]} { 25 | return Int 26 | } 27 | 28 | if {[string is double -strict $defaultValue]} { 29 | return Double 30 | } 31 | 32 | if {[string is boolean -strict $defaultValue]} { 33 | return Bool 34 | } 35 | 36 | 37 | return String 38 | } 39 | 40 | # 41 | # does_proc_return_something - meatball check to see if a 42 | # tcl proc returns a value. 43 | # 44 | proc does_proc_return_something {proc} { 45 | set body [info body $proc] 46 | 47 | return [regexp {return } $body] 48 | } 49 | 50 | # 51 | # gen - generate interface functions 52 | # 53 | proc gen {proc} { 54 | variable hints 55 | 56 | set args [info args $proc] 57 | 58 | if {[info exists hints($proc)]} { 59 | array set myHints $hints($proc) 60 | } 61 | 62 | set swift_name tcl::$proc 63 | regsub -all "::*" $swift_name "_" swift_name 64 | set string "\n// $swift_name\n// Wrapper for $proc\nfunc $swift_name (springboardInterp: TclInterp" 65 | 66 | puts stderr $args 67 | foreach arg $args { 68 | append string ", " 69 | 70 | if {![info default $proc $arg default]} { 71 | unset -nocomplain default 72 | } 73 | 74 | # if there's a hint for the type, use that, 75 | # else if there's a default value try to 76 | # sniff the type out of that else say 77 | # it's a String 78 | if {[info exists myHints($arg)]} { 79 | set type $myHints($arg) 80 | } elseif {[info exists default]} { 81 | set type [guess_default_type $default] 82 | } else { 83 | set type String 84 | } 85 | set myTypes($arg) $type 86 | 87 | append string "$arg: $type" 88 | 89 | if {[info exists default]} { 90 | if {$type == "String"} { 91 | append string " = \"$default\"" 92 | } else { 93 | append string " = $default" 94 | } 95 | } 96 | } 97 | 98 | append string ") throws" 99 | 100 | if {[info exists myHints(->)]} { 101 | append string " -> $myHints(->)" 102 | set return_type "$myHints(->)" 103 | } else { 104 | if {[does_proc_return_something $proc]} { 105 | append string " -> String" 106 | set return_type "String" 107 | } 108 | } 109 | 110 | append string " {\n" 111 | 112 | set body {} 113 | lappend body "let vec = springboardInterp.newObject()" 114 | lappend body "try vec.lappend(\"$proc\")" 115 | 116 | foreach arg $args { 117 | lappend body "try vec.lappend($arg)" 118 | } 119 | 120 | lappend body "Tcl_EvalObjEx(springboardInterp.interp, vec.get(), 0)" 121 | if [info exists return_type] { 122 | lappend body "return try springboardInterp.getResult()" 123 | } 124 | append string " [join $body "\n "]\n" 125 | append string "}" 126 | 127 | 128 | 129 | return $string 130 | } 131 | 132 | # 133 | # enumerate_procs - recursively enumerate all procs within a namespace and 134 | # all of its descendant namespaces (defaulting to the top-level namespace), 135 | # returning them as a list 136 | # 137 | proc enumerate_procs {{ns ::}} { 138 | set list [info procs ${ns}::*] 139 | 140 | foreach childNamespace [namespace children $ns] { 141 | lappend list {*}[enumerate_procs $childNamespace] 142 | } 143 | 144 | return $list 145 | } 146 | 147 | } 148 | 149 | 150 | -------------------------------------------------------------------------------- /Sources/tcl-array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // tcl-array.swift 3 | // tcl-swift-bridge 4 | // 5 | // Created by Peter da Silva on 5/17/16. 6 | // Copyright © 2016 FlightAware. All rights reserved. 7 | // 8 | // Free under the Berkeley license. 9 | // 10 | 11 | import Foundation 12 | import Tcl8_6 13 | 14 | 15 | // TclArray - Tcl object class 16 | 17 | public class TclArray: Sequence { 18 | let name: String 19 | let Interp: TclInterp 20 | let interp: UnsafeMutablePointer 21 | 22 | // init - initialize from empty or existing array 23 | public init(_ name: String, Interp: TclInterp, namespace: String? = nil) { 24 | self.Interp = Interp; 25 | self.interp = Interp.interp 26 | if let ns = namespace { 27 | self.name = ns + "::" + name; 28 | } else { 29 | self.name = name; 30 | } 31 | } 32 | 33 | public func set(_ dict: [String : String]) throws { 34 | try Interp.set(array: name, from: dict) 35 | } 36 | 37 | // init - initialize from string 38 | public convenience init(_ name: String, Interp: TclInterp, namespace: String? = nil, string: String) throws { 39 | self.init(name, Interp: Interp, namespace: namespace) 40 | try self.set(Interp.newObject(string).get() as [String: TclObj]) 41 | } 42 | 43 | // init - initialize from dictionary 44 | public convenience init(_ name: String, Interp: TclInterp, namespace: String? = nil, dict: [String: String]) throws { 45 | self.init(name, Interp: Interp, namespace: namespace) 46 | try self.set(dict) 47 | } 48 | 49 | // init - initialize from dictionary 50 | public convenience init(_ name: String, Interp: TclInterp, namespace: String? = nil, dict: [String: TclObj]) throws { 51 | self.init(name, Interp: Interp, namespace: namespace) 52 | try self.set(dict) 53 | } 54 | 55 | public func set(_ dict: [String : TclObj]) throws { 56 | try Interp.set(array: name, from: dict) 57 | } 58 | 59 | // names - generate a list of names for the keys in the array. 60 | // This is ugly because there doesn't seem to be a C API for enumerating arrays 61 | public func names() throws -> [String] { 62 | let cmd = TclObj("array names", Interp: Interp) 63 | try cmd.lappend(self.name) 64 | let res: TclObj = try Interp.eval(code: cmd.get()) 65 | return try res.get() 66 | } 67 | 68 | // get - return a dict [String: String] 69 | public func get() throws -> [String: String] { 70 | let old: [String: TclObj] = self.get() 71 | var new: [String: String] = [:] 72 | for (key, obj) in old { 73 | try new[key] = obj.get() 74 | } 75 | return new 76 | } 77 | 78 | // get - return a dict [String: TclObj] 79 | public func get() -> [String: TclObj] { 80 | var dict: [String: TclObj] = [:] 81 | if let names = try? self.names() { 82 | for name in names { 83 | if let val: TclObj = self.getValue(name) { 84 | dict[name] = val 85 | } 86 | } 87 | } 88 | return dict 89 | } 90 | 91 | public func getValue(_ key: String) -> TclObj? { 92 | return Interp.get(variable: name, element: key) 93 | } 94 | 95 | public func setValue(_ key: String, obj: TclObj) throws { 96 | try Interp.set(variable: name, element: key, obj: obj) 97 | } 98 | 99 | public func setValue(_ key: String, value: String) throws { 100 | try Interp.set(variable: name, element: key, value: value) 101 | } 102 | 103 | public func setValue(_ key: String, value: Int) throws { 104 | try Interp.set(variable: name, element: key, value: value) 105 | } 106 | 107 | public func setValue(_ key: String, value: Double) throws { 108 | try Interp.set(variable: name, element: key, value: value) 109 | } 110 | 111 | public func setValue(_ key: String, value: Bool) throws { 112 | try Interp.set(variable: name, element: key, value: value) 113 | } 114 | 115 | public subscript (key: String) -> TclObj? { 116 | get { 117 | return getValue(key) 118 | } 119 | set { 120 | if let obj = newValue { 121 | do { 122 | try setValue(key, obj: obj) 123 | } catch { 124 | } 125 | } 126 | } 127 | } 128 | 129 | public subscript (key: String) -> String? { 130 | get { 131 | do { 132 | return try getValue(key)?.get() 133 | } catch { 134 | return nil 135 | } 136 | } 137 | set { 138 | if let string = newValue { 139 | do { 140 | try setValue(key, value: string) 141 | } catch { 142 | } 143 | } 144 | } 145 | } 146 | 147 | public subscript (key: String) -> Int? { 148 | get { 149 | do { 150 | return try getValue(key)?.get() 151 | } catch { 152 | return nil 153 | } 154 | } 155 | set { 156 | if let int = newValue { 157 | do { 158 | try setValue(key, value: int) 159 | } catch { 160 | } 161 | } 162 | } 163 | } 164 | 165 | public subscript (key: String) -> Double? { 166 | get { 167 | do { 168 | return try getValue(key)?.get() 169 | } catch { 170 | return nil 171 | } 172 | } 173 | set { 174 | if let double = newValue { 175 | do { 176 | try setValue(key, value: double) 177 | } catch { 178 | } 179 | } 180 | } 181 | } 182 | 183 | public subscript (key: String) -> Bool? { 184 | get { 185 | do { 186 | return try getValue(key)?.get() 187 | } catch { 188 | return nil 189 | } 190 | } 191 | set { 192 | if let bool = newValue { 193 | do { 194 | try setValue(key, value: bool) 195 | } catch { 196 | } 197 | } 198 | } 199 | } 200 | 201 | // Generator for maps, forEach, etc... returns a tuple 202 | public func makeIterator() -> AnyIterator<(String, TclObj)> { 203 | guard let nameList = try? self.names() else { 204 | // Can't initialize the generator, so return a dummy generator that always returns nil 205 | return AnyIterator<(String, TclObj)> { return nil } 206 | } 207 | 208 | var next = 0 209 | 210 | return AnyIterator<(String, TclObj)> { 211 | var value: TclObj? = nil 212 | var name: String? = nil 213 | // looping over name list in case someone unset an array element behind my back 214 | while value == nil { 215 | if next >= nameList.count { 216 | return nil 217 | } 218 | name = nameList[next] 219 | next += 1 220 | 221 | // name can never be nil here necause it's just been assigned from nameList which is a [String] 222 | value = self.getValue(name!) 223 | } 224 | // At this point I believe it is impossible for name to be nil but I'm checking anyway 225 | if name != nil && value != nil { 226 | return (name!, value!); 227 | } 228 | return nil 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Sources/tcl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // tcl.swift 3 | // tcl-swift-play 4 | // 5 | // Created by Karl Lehenbauer on 4/6/16. 6 | // Copyright © 2016 FlightAware. All rights reserved. 7 | // 8 | // Free under the Berkeley license. 9 | // 10 | 11 | import Foundation 12 | import Tcl8_6 13 | 14 | public enum TclReturn: Int32 { 15 | case tcl_ok = 0 16 | case tcl_error = 1 17 | case tcl_return = 2 18 | case tcl_break = 3 19 | case tcl_continue = 4 20 | } 21 | 22 | // Tcl commands functions written in Swift can return Int, Double, String, Bool, TclObj or a TCL_RETURN-type code 23 | 24 | public typealias SwiftTclFuncReturningTclReturn = (TclInterp, [TclObj]) throws -> TclReturn 25 | public typealias SwiftTclFuncReturningInt = (TclInterp, [TclObj]) throws -> Int 26 | public typealias SwiftTclFuncReturningDouble = (TclInterp, [TclObj]) throws -> Double 27 | public typealias SwiftTclFuncReturningString = (TclInterp, [TclObj]) throws -> String 28 | public typealias SwiftTclFuncReturningBool = (TclInterp, [TclObj]) throws -> Bool 29 | public typealias SwiftTclFuncReturningTclObj = (TclInterp, [TclObj]) throws -> TclObj 30 | //public typealias Tcl_ObjP = UnsafeMutablePointer 31 | 32 | public enum SwiftTclFunctionType { 33 | case tclReturn(SwiftTclFuncReturningTclReturn) 34 | case int(SwiftTclFuncReturningInt) 35 | case double(SwiftTclFuncReturningDouble) 36 | case string(SwiftTclFuncReturningString) 37 | case bool(SwiftTclFuncReturningBool) 38 | case tclObj(SwiftTclFuncReturningTclObj) 39 | } 40 | 41 | public enum TclError: Error { 42 | case wrongNumArgs(nLeadingArguments: Int, message: String) 43 | case errorMessage(message: String, errorCode: String) // set error message in interpreter result 44 | case unknownReturnCode(code: Int32) 45 | case notString(string: String) 46 | case nullPointer // Object or string passed to a handler is a null pointer 47 | case error // error already set in interpreter result 48 | } 49 | 50 | enum TclControlFlow: Error { 51 | case tcl_return 52 | case tcl_break 53 | case tcl_continue 54 | } 55 | 56 | public struct VariableFlags : OptionSet { 57 | public let rawValue: Int32; 58 | public init(rawValue : Int32) { self.rawValue = rawValue } 59 | 60 | static let GlobalOnly = VariableFlags(rawValue: TCL_GLOBAL_ONLY) 61 | static let NamespaceOnly = VariableFlags(rawValue: TCL_NAMESPACE_ONLY) 62 | static let LeaveErroMsg = VariableFlags(rawValue: TCL_LEAVE_ERR_MSG) 63 | static let AppendValue = VariableFlags(rawValue: TCL_APPEND_VALUE) 64 | static let ListElement = VariableFlags(rawValue: TCL_LIST_ELEMENT) 65 | static let TraceReads = VariableFlags(rawValue: TCL_TRACE_READS) 66 | static let TraceWrites = VariableFlags(rawValue: TCL_TRACE_WRITES) 67 | static let TraceUnsets = VariableFlags(rawValue: TCL_TRACE_UNSETS) 68 | static let TraceDestroyed = VariableFlags(rawValue: TCL_TRACE_DESTROYED) 69 | static let InterpDestroyed = VariableFlags(rawValue: TCL_INTERP_DESTROYED) 70 | static let TraceArray = VariableFlags(rawValue: TCL_TRACE_ARRAY) 71 | static let TraceResultDynamic = VariableFlags(rawValue: TCL_TRACE_RESULT_DYNAMIC) 72 | static let TraceResultObject = VariableFlags(rawValue: TCL_TRACE_RESULT_OBJECT) 73 | 74 | //static let None = VariableFlags(rawValue: 0) 75 | } 76 | 77 | public struct SubstFlags : OptionSet { 78 | public let rawValue: Int32; 79 | public init(rawValue : Int32) { self.rawValue = rawValue } 80 | 81 | static let Commands = SubstFlags(rawValue:TCL_SUBST_COMMANDS) 82 | static let Variables = SubstFlags(rawValue:TCL_SUBST_VARIABLES) 83 | static let Backslashes = SubstFlags(rawValue:TCL_SUBST_BACKSLASHES) 84 | static let All = SubstFlags(rawValue:TCL_SUBST_ALL) 85 | } 86 | 87 | // TclCommandBlock - when creating a Tcl command -> Swift 88 | class TclCommandBlock { 89 | let swiftTclCallFunction: SwiftTclFunctionType 90 | 91 | init(function: @escaping SwiftTclFuncReturningTclReturn) { 92 | swiftTclCallFunction = .tclReturn(function) 93 | } 94 | 95 | init(function: @escaping SwiftTclFuncReturningInt) { 96 | swiftTclCallFunction = .int(function) 97 | } 98 | 99 | init(function: @escaping SwiftTclFuncReturningDouble) { 100 | swiftTclCallFunction = .double(function) 101 | } 102 | 103 | init(function: @escaping SwiftTclFuncReturningString) { 104 | swiftTclCallFunction = .string(function) 105 | } 106 | 107 | init(function: @escaping SwiftTclFuncReturningBool) { 108 | swiftTclCallFunction = .bool(function) 109 | } 110 | 111 | init(function: @escaping SwiftTclFuncReturningTclObj) { 112 | swiftTclCallFunction = .tclObj(function) 113 | } 114 | 115 | func invoke(Interp: TclInterp, objv: [TclObj]) throws -> TclReturn { 116 | switch swiftTclCallFunction { 117 | case .tclReturn(let function): 118 | return try function(Interp, objv) 119 | default: 120 | abort() 121 | } 122 | } 123 | 124 | func invoke(Interp: TclInterp, objv: [TclObj]) throws -> Int { 125 | switch swiftTclCallFunction { 126 | case .int(let function): 127 | return try function(Interp, objv) 128 | default: 129 | abort() 130 | } 131 | } 132 | 133 | func invoke(Interp: TclInterp, objv: [TclObj]) throws -> Double { 134 | switch swiftTclCallFunction { 135 | case .double(let function): 136 | return try function(Interp, objv) 137 | default: 138 | abort() 139 | } 140 | } 141 | 142 | func invoke(Interp: TclInterp, objv: [TclObj]) throws -> String { 143 | switch swiftTclCallFunction { 144 | case .string(let function): 145 | return try function(Interp, objv) 146 | default: 147 | abort() 148 | } 149 | } 150 | 151 | func invoke(Interp: TclInterp, objv: [TclObj]) throws -> Bool { 152 | switch swiftTclCallFunction { 153 | case .bool(let function): 154 | return try function(Interp, objv) 155 | default: 156 | abort() 157 | } 158 | } 159 | 160 | func invoke(Interp: TclInterp, objv: [TclObj]) throws -> TclObj { 161 | switch swiftTclCallFunction { 162 | case .tclObj(let function): 163 | return try function(Interp, objv) 164 | default: 165 | abort() 166 | } 167 | } 168 | } 169 | 170 | // tclobjp_to_String - return the value of a Tcl_Obj * as a String or nil 171 | 172 | func tclobjp_to_String (_ tclObjP: UnsafeMutablePointer?) throws -> String { 173 | guard let tclObjP = tclObjP else { throw TclError.nullPointer } 174 | 175 | return String(cString: Tcl_GetString(tclObjP)) 176 | } 177 | 178 | // tclobjp_to_Int - return the value of a Tcl_Obj * as an Int or nil 179 | 180 | func tclobjp_to_Int (_ tclObjP: UnsafeMutablePointer?, interp: UnsafeMutablePointer? = nil) throws -> Int { 181 | var longVal: CLong = 0 182 | guard let tclObjP = tclObjP else { throw TclError.nullPointer } 183 | 184 | let result = Tcl_GetLongFromObj (interp, tclObjP, &longVal) 185 | if (result == TCL_ERROR) { 186 | if (interp == nil) { 187 | throw TclError.errorMessage(message: "expected integer", errorCode: "TCL VALUE NUMBER") 188 | } else { 189 | throw TclError.error 190 | } 191 | } 192 | return longVal 193 | } 194 | 195 | // tclobjp_to_Double - return the value of a Tcl_Obj * as a Double or nil 196 | 197 | func tclobjp_to_Double (_ tclObjP: UnsafeMutablePointer?, interp: UnsafeMutablePointer? = nil) throws -> Double { 198 | var doubleVal: Double = 0 199 | guard let tclObjP = tclObjP else { throw TclError.nullPointer } 200 | 201 | let result = Tcl_GetDoubleFromObj (interp, tclObjP, &doubleVal) 202 | if (result == TCL_ERROR) { 203 | if (interp == nil) { 204 | throw TclError.errorMessage(message: "expected double", errorCode: "TCL VALUE NUMBER") 205 | } else { 206 | throw TclError.error 207 | } 208 | 209 | } 210 | return doubleVal 211 | } 212 | 213 | // tclobjp_to_Bool - return the value of a Tcl_Obj * as a Bool or nil 214 | 215 | func tclobjp_to_Bool (_ tclObjP: UnsafeMutablePointer?, interp: UnsafeMutablePointer? = nil) throws -> Bool { 216 | var boolVal: Int32 = 0 217 | guard let tclObjP = tclObjP else { throw TclError.nullPointer } 218 | 219 | let result = Tcl_GetBooleanFromObj (interp, tclObjP, &boolVal) 220 | if (result == TCL_ERROR) { 221 | if (interp == nil) { 222 | throw TclError.errorMessage(message: "expected boolean", errorCode: "TCL VALUE NUMBER") 223 | } else { 224 | throw TclError.error 225 | } 226 | 227 | } 228 | return boolVal == 0 ? true : false 229 | } 230 | 231 | // tclobjp(string:) - create a Tcl_Obj * from a Swift String 232 | 233 | func tclobjp (string: String) throws -> UnsafeMutablePointer { 234 | return Tcl_NewStringObj (string, -1) 235 | } 236 | 237 | // Protocol for types that Tcl knows 238 | protocol TclType { 239 | init? (_ obj: TclObj) 240 | mutating func fromTclObj(_ obj: TclObj) 241 | } 242 | 243 | // extend Swift objects to satisfy protocol TclType 244 | extension String: TclType { 245 | public init? (_ obj: TclObj) { 246 | guard let value: String = try? obj.get() else {return nil} 247 | self.init(value) 248 | } 249 | 250 | mutating func fromTclObj(_ obj: TclObj) { 251 | self = obj.stringValue! 252 | } 253 | } 254 | 255 | extension Int: TclType { 256 | public init? (_ obj: TclObj) { 257 | guard let value: Int = try? obj.get() else {return nil} 258 | self.init(value) 259 | } 260 | 261 | mutating func fromTclObj(_ obj: TclObj) { 262 | self = obj.intValue! 263 | } 264 | } 265 | 266 | extension Double: TclType { 267 | public init? (_ obj: TclObj) { 268 | guard let value: Double = try? obj.get() else {return nil} 269 | self.init(value) 270 | } 271 | 272 | mutating func fromTclObj(_ obj: TclObj) { 273 | self = obj.doubleValue! 274 | } 275 | } 276 | 277 | extension Bool: TclType { 278 | public init? (_ obj: TclObj) { 279 | guard let value: Bool = try? obj.get() else {return nil} 280 | self.init(value) 281 | } 282 | 283 | mutating func fromTclObj(_ obj: TclObj) { 284 | self = obj.boolValue! 285 | } 286 | } 287 | 288 | 289 | // swift_tcl_bridger - this is the trampoline that gets called by Tcl when invoking a created Swift command 290 | // this declaration is the Swift equivalent of Tcl_ObjCmdProc *proc 291 | func swift_tcl_bridger (clientData: ClientData?, interp: UnsafeMutablePointer?, objc: Int32, objv: UnsafePointer?>?) -> Int32 { 292 | let Interp = TclInterp(interp: interp!, printErrors: false) 293 | let tcb = Unmanaged.fromOpaque(clientData!).takeUnretainedValue() 294 | 295 | // construct an array containing the arguments 296 | var objvec = [TclObj]() 297 | if let objv = objv { 298 | for i in 0.. 19 | let ownInterpreter: Bool 20 | public var printErrors = true 21 | 22 | // init - create and initialize a full Tcl interpreter 23 | public init(printErrors: Bool = true) { 24 | self.printErrors = printErrors 25 | interp = Tcl_CreateInterp() 26 | Tcl_Init(interp) 27 | ownInterpreter = true 28 | } 29 | 30 | // init - wrap an already created interpreter with a TclInterp class 31 | public init(interp: UnsafeMutablePointer, printErrors: Bool = true) { 32 | self.printErrors = printErrors 33 | self.interp = interp; 34 | ownInterpreter = false 35 | } 36 | 37 | // deinit - upon deletion of this object, delete the corresponding 38 | // Tcl interpreter 39 | deinit { 40 | if ownInterpreter { 41 | Tcl_DeleteInterp (interp) 42 | } 43 | } 44 | 45 | // getRawInterpPtr - return Tcl_Interp * 46 | private func getRawInterpPtr() -> UnsafeMutablePointer { 47 | return interp 48 | } 49 | 50 | // rawEval - evaluate a string with the Tcl interpreter 51 | // 52 | // Returns void, throws a TclError or TclResultCode 53 | // 54 | public func rawEval(code: TclObj, caller: String = #function) throws { 55 | let ret = Tcl_EvalObj(interp, code.obj) 56 | 57 | switch ret { 58 | case TCL_RETURN: 59 | throw TclControlFlow.tcl_return 60 | case TCL_BREAK: 61 | throw TclControlFlow.tcl_break 62 | case TCL_CONTINUE: 63 | throw TclControlFlow.tcl_continue 64 | case TCL_ERROR: 65 | self.addErrorInfo(" called from Swift '\(caller)'") 66 | if printErrors { 67 | print("Error: \(self.result)") 68 | let errorInfo: String = try self.get(variable: "errorInfo") 69 | print(errorInfo) 70 | } 71 | 72 | let errorCode = self.get(variable: "errorCode") ?? "" 73 | throw TclError.errorMessage(message: self.result, errorCode: errorCode) 74 | case TCL_OK: 75 | break 76 | default: 77 | throw TclError.unknownReturnCode(code:ret) 78 | } 79 | } 80 | 81 | // Safer way to call rawEval, passing a list of strings 82 | public func rawEval(list: [String], caller: String = #function) throws { 83 | try rawEval(code: TclObj(list, Interp: self), caller: caller) 84 | } 85 | 86 | // Usual way to call rawEval, passing a string 87 | public func rawEval(code: String, caller: String = #function) throws { 88 | try rawEval(code: TclObj(code, Interp: self), caller: caller) 89 | } 90 | 91 | // eval - evaluate the string via the Tcl Interpreter, return the Tcl result of the 92 | // evaluation. Throws TclError or TclControlFlow. 93 | public func eval(code: String, caller: String = #function) throws -> String { 94 | try self.rawEval(code: code, caller: caller) 95 | return try self.getResult() 96 | } 97 | 98 | public func eval(code: String, caller: String = #function) throws -> Int { 99 | try self.rawEval(code: code, caller: caller) 100 | return try self.getResult() 101 | } 102 | 103 | public func eval(code: String, caller: String = #function) throws -> Double { 104 | try self.rawEval(code: code, caller: caller) 105 | return try self.getResult() 106 | } 107 | 108 | public func eval(code: String, caller: String = #function) throws -> Bool { 109 | try self.rawEval(code: code, caller: caller) 110 | return try self.getResult() 111 | } 112 | 113 | public func eval(code: String, caller: String = #function) throws -> TclObj { 114 | try self.rawEval(code: code, caller: caller) 115 | return self.resultObj 116 | } 117 | 118 | // eval - evaluate a TclObj via the Tcl Interpreter, return the Tcl result of the 119 | // evaluation. Throws TclError or TclControlFlow. 120 | public func eval(code: TclObj, caller: String = #function) throws -> String { 121 | try self.rawEval(code: code, caller: caller) 122 | return try self.getResult() 123 | } 124 | 125 | public func eval(code: TclObj, caller: String = #function) throws -> Int { 126 | try self.rawEval(code: code, caller: caller) 127 | return try self.getResult() 128 | } 129 | 130 | public func eval(code: TclObj, caller: String = #function) throws -> Double { 131 | try self.rawEval(code: code, caller: caller) 132 | return try self.getResult() 133 | } 134 | 135 | public func eval(code: TclObj, caller: String = #function) throws -> Bool { 136 | try self.rawEval(code: code, caller: caller) 137 | return try self.getResult() 138 | } 139 | 140 | public func eval(code: TclObj, caller: String = #function) throws -> TclObj { 141 | try self.rawEval(code: code, caller: caller) 142 | return self.resultObj 143 | } 144 | 145 | // result - grab the interpreter result as a string 146 | public var result: String { 147 | get { 148 | guard let rawString = Tcl_GetString(Tcl_GetObjResult(interp)) else { return "" } 149 | return (String(cString: rawString)) 150 | } 151 | set { 152 | let obj: UnsafeMutablePointer = Tcl_NewStringObj(newValue, -1) 153 | Tcl_SetObjResult(interp, obj) 154 | } 155 | } 156 | 157 | // resultObj - set the interpreter result to the TclObj or return a TclObj based on the interpreter result 158 | public var resultObj: TclObj { 159 | get { 160 | return TclObj(Tcl_GetObjResult(interp), Interp: self) 161 | } 162 | set { 163 | Tcl_SetObjResult(interp,resultObj.get()) 164 | } 165 | } 166 | 167 | public func getResult() throws -> String { 168 | let obj: UnsafeMutablePointer = Tcl_GetObjResult(interp) 169 | return try tclobjp_to_String(obj) 170 | } 171 | 172 | public func getResult() throws -> Int { 173 | let obj: UnsafeMutablePointer = Tcl_GetObjResult(interp) 174 | return try tclobjp_to_Int(obj) 175 | } 176 | 177 | public func getResult() throws -> Double { 178 | let obj: UnsafeMutablePointer = Tcl_GetObjResult(interp) 179 | return try tclobjp_to_Double(obj) 180 | } 181 | 182 | public func getResult() throws -> Bool { 183 | let obj: UnsafeMutablePointer = Tcl_GetObjResult(interp) 184 | return try tclobjp_to_Bool(obj) 185 | } 186 | 187 | // setResult - set the interpreter result from a Double 188 | func setResult(_ val: Double) { 189 | Tcl_SetDoubleObj (Tcl_GetObjResult(interp), val) 190 | } 191 | 192 | // setResult - set the interpreter result from an Int 193 | public func setResult(_ val: Int) { 194 | Tcl_SetLongObj (Tcl_GetObjResult(interp), val) 195 | } 196 | 197 | // setResult - set the interpreter result from a Bool 198 | public func setResult(_ val: Bool) { 199 | Tcl_SetBooleanObj (Tcl_GetObjResult(interp), val ? 1 : 0) 200 | } 201 | 202 | // setErrorCode - set the Tcl error code 203 | 204 | public func setErrorCode(_ val: String) throws { 205 | Tcl_SetObjErrorCode (interp, try tclobjp(string: val)) 206 | } 207 | 208 | // addErrorInfo() - append a message to the error information 209 | 210 | public func addErrorInfo(_ message: String) { 211 | Tcl_AddObjErrorInfo (interp, message, -1) 212 | } 213 | 214 | // get(variable:...) - return a Tcl variable or array element as an 215 | // UnsafeMutablePointer (i.e. a Tcl_Obj *), or nil if it doesn't exist. 216 | // if elementName is specified, var is an element of an array, otherwise var is a variable 217 | 218 | private func get(variable varName: String, element elementName: String? = nil, flags: VariableFlags = []) -> UnsafeMutablePointer? { 219 | 220 | return Tcl_GetVar2Ex(interp, varName, elementName, flags.rawValue) 221 | } 222 | 223 | // get(variable:...) - return a Tcl variable or in a TclObj object, or nil 224 | public func get(variable varName: String, element elementName: String? = nil, flags: VariableFlags = []) -> TclObj? { 225 | let obj: UnsafeMutablePointer? = self.get(variable: varName, element: elementName, flags: flags) 226 | 227 | guard (obj != nil) else {return nil} 228 | 229 | return TclObj(obj!, Interp: self) 230 | } 231 | 232 | // get(variable:...) - return Tcl variable or array element as an Int or throw an error 233 | public func get(variable varName: String, element elementName: String? = nil, flags: VariableFlags = []) throws -> Int { 234 | let obj: UnsafeMutablePointer? = self.get(variable: varName, element: elementName, flags: flags) 235 | 236 | return try tclobjp_to_Int(obj) 237 | } 238 | 239 | // get(variable:...) - return a var as a Double, or throw an error if unable 240 | public func get(variable arrayName: String, element elementName: String? = nil) throws -> Double { 241 | let objp: UnsafeMutablePointer? = self.get(variable: arrayName, element: elementName) 242 | 243 | return try tclobjp_to_Double(objp) 244 | } 245 | 246 | // get(variable:...) - return a TclObj containing var as a String or throw an error if unable 247 | // the error seems unlikely but could be like a UTF-8 conversion error or something. 248 | public func get(variable arrayName: String, element elementName: String? = nil) throws -> String { 249 | let objp: UnsafeMutablePointer? = self.get(variable: arrayName, element: elementName) 250 | 251 | return try tclobjp_to_String(objp) 252 | } 253 | 254 | // get(variable:...) - return a TclObj containing var as a String, or nil 255 | public func get(variable arrayName: String, element elementName: String? = nil) -> String? { 256 | let objp: UnsafeMutablePointer? = self.get(variable: arrayName, element: elementName) 257 | 258 | do { 259 | return try tclobjp_to_String(objp) 260 | } catch { 261 | return nil 262 | } 263 | } 264 | 265 | // set(variable:...) - set a variable or array element in the Tcl interpreter 266 | // from an UnsafeMutablePointer (i.e. a Tcl_Obj *) 267 | // returns true or false based on whether it succeeded or not 268 | func set(variable varName: String, element elementName: String? = nil, value: UnsafeMutablePointer, flags: VariableFlags = []) throws { 269 | let ret = Tcl_SetVar2Ex(interp, varName, elementName!, value, flags.rawValue) 270 | if ret == nil { 271 | throw TclError.error 272 | } 273 | } 274 | 275 | // set(variable:...) - set a variable or array element in the Tcl interpreter to the specified TclObj 276 | public func set(variable varName: String, element elementName: String? = nil, value: TclObj, flags: VariableFlags = []) throws { 277 | return try self.set(variable: varName, element: elementName, value: value.obj, flags: flags) 278 | } 279 | 280 | // set(variable:...) - set a variable or array element in the Tcl interpreter to the specified String 281 | public func set(variable varName: String, element elementName: String? = nil, value: String, flags: VariableFlags = []) throws { 282 | let obj = try tclobjp(string: value) 283 | return try self.set(variable: varName, element: elementName, value: obj, flags: flags) 284 | } 285 | 286 | // set(variable:...) - set a variable or array element in the Tcl interpreter to the specified Int 287 | public func set(variable varName: String, element elementName: String? = nil, value: Int, flags: VariableFlags = []) throws { 288 | let obj = Tcl_NewIntObj(Int32(value)) 289 | return try self.set(variable: varName, element: elementName, value: obj!, flags: flags) 290 | } 291 | 292 | // set(variable:...) - set a variable or array element in the Tcl interpreter to the specified Bool 293 | public func set(variable varName: String, element elementName: String? = nil, value: Bool, flags: VariableFlags = []) throws { 294 | let obj = Tcl_NewBooleanObj(value ? 1 : 0) 295 | return try self.set(variable: varName, element: elementName, value: obj!, flags: flags) 296 | } 297 | 298 | // set(variable:...) - set a variable or array element in the Tcl interpreter to the specified Double 299 | public func set(variable varName: String, element elementName: String? = nil, value: Double, flags: VariableFlags = []) throws { 300 | let obj = Tcl_NewDoubleObj(value) 301 | return try self.set(variable: varName, element: elementName, value: obj!, flags: flags) 302 | } 303 | 304 | // set(variable:...) - set a variable or array element in the Tcl interpreter to the specified TclObj 305 | public func set(variable varName: String, element elementName: String? = nil, obj: TclObj, flags: VariableFlags = []) throws { 306 | return try self.set(variable: varName, element: elementName, value: obj.get() as UnsafeMutablePointer, flags: flags) 307 | } 308 | 309 | // set(array:..., from:...) - set a String/TclObj dictionary into a Tcl array 310 | public func set (array arrayName: String, from dictionary: [String: TclObj], flags: VariableFlags = []) throws { 311 | for (key, val) in dictionary { 312 | try self.set(variable: arrayName, element: key, value: val, flags: flags) 313 | } 314 | } 315 | 316 | // set(array:..., from:...) - set a String/String dictionary into a Tcl array 317 | public func set(array arrayName: String, from dictionary: [String: String], flags: VariableFlags = []) throws { 318 | for (key, val) in dictionary { 319 | try self.set(variable: arrayName, element: key, value: val, flags: flags) 320 | } 321 | } 322 | 323 | // set(array:..., from:...) - set a String/Int dictionary into a Tcl array 324 | public func set(array arrayName: String, from dictionary: [String: Int], flags: VariableFlags = []) throws { 325 | for (key, val) in dictionary { 326 | try self.set(variable: arrayName, element: key, value: val, flags: flags) 327 | } 328 | } 329 | 330 | // set(array:..., from:...) - set a String/Double dictionary into a Tcl array 331 | public func set (array arrayName: String, dictionary: [String: Double], flags: VariableFlags = []) throws { 332 | for (key, val) in dictionary { 333 | try self.set(variable: arrayName, element: key, value: val, flags: flags) 334 | } 335 | } 336 | 337 | // create_command - create a new Tcl command that will be handled by the specified Swift function 338 | // NB - this is kludgey, too much replication with variants 339 | public func createCommand(named name: String, using swiftTclFunction: @escaping SwiftTclFuncReturningTclReturn) { 340 | let cmdBlock = TclCommandBlock(function: swiftTclFunction) 341 | let clientData = Unmanaged.passRetained(cmdBlock).toOpaque() 342 | Tcl_CreateObjCommand(interp, name, swift_tcl_bridger, clientData, nil) 343 | } 344 | 345 | // create_command - create a new Tcl command that will be handled by the specified Swift function 346 | public func createCommand(named name: String, using swiftTclFunction: @escaping SwiftTclFuncReturningDouble) { 347 | let cmdBlock = TclCommandBlock(function: swiftTclFunction) 348 | let clientData = Unmanaged.passRetained(cmdBlock).toOpaque() 349 | Tcl_CreateObjCommand(interp, name, swift_tcl_bridger, clientData, nil) 350 | } 351 | 352 | // create_command - create a new Tcl command that will be handled by the specified Swift function 353 | public func createCommand(named name: String, using swiftTclFunction: @escaping SwiftTclFuncReturningString) { 354 | let cmdBlock = TclCommandBlock(function: swiftTclFunction) 355 | let clientData = Unmanaged.passRetained(cmdBlock).toOpaque() 356 | Tcl_CreateObjCommand(interp, name, swift_tcl_bridger, clientData, nil) 357 | } 358 | 359 | // create_command - create a new Tcl command that will be handled by the specified Swift function 360 | public func createCommand(named name: String, using swiftTclFunction: @escaping SwiftTclFuncReturningInt) { 361 | let cmdBlock = TclCommandBlock(function: swiftTclFunction) 362 | let clientData = Unmanaged.passRetained(cmdBlock).toOpaque() 363 | Tcl_CreateObjCommand(interp, name, swift_tcl_bridger, clientData, nil) 364 | } 365 | 366 | // create_command - create a new Tcl command that will be handled by the specified Swift function 367 | public func createCommand(named name: String, using swiftTclFunction: @escaping SwiftTclFuncReturningBool) { 368 | let cmdBlock = TclCommandBlock(function: swiftTclFunction) 369 | let clientData = Unmanaged.passRetained(cmdBlock).toOpaque() 370 | Tcl_CreateObjCommand(interp, name, swift_tcl_bridger, clientData, nil) 371 | } 372 | 373 | // create_command - create a new Tcl command that will be handled by the specified Swift function 374 | public func createCommand(named name: String, using swiftTclFunction: @escaping SwiftTclFuncReturningTclObj) { 375 | let cmdBlock = TclCommandBlock(function: swiftTclFunction) 376 | let clientData = Unmanaged.passRetained(cmdBlock).toOpaque() 377 | Tcl_CreateObjCommand(interp, name, swift_tcl_bridger, clientData, nil) 378 | } 379 | 380 | func subst (_ substInTclObj: TclObj, flags: SubstFlags = [.All]) throws -> TclObj { 381 | let substOutObj = Tcl_SubstObj (interp, substInTclObj.obj, flags.rawValue) 382 | guard let result = substOutObj else { 383 | throw TclError.error 384 | } 385 | return TclObj(result, Interp: self) 386 | } 387 | 388 | public func subst (_ substIn: String, flags: SubstFlags = [.All]) throws -> String { 389 | let substOutObj: TclObj = try self.subst (TclObj(substIn, Interp: self), flags: flags) 390 | return try substOutObj.get() 391 | } 392 | 393 | // Wrappers for TclObj - this is kludgey 394 | public func newObject() -> TclObj { return TclObj(Interp: self) } 395 | public func newObject(_ value: Int) -> TclObj { return TclObj(value, Interp: self) } 396 | public func newObject(_ value: String) -> TclObj { return TclObj(value, Interp: self) } 397 | public func newObject(_ value: Double) -> TclObj { return TclObj(value, Interp: self) } 398 | public func newObject(_ value: Bool) -> TclObj { return TclObj(value, Interp: self) } 399 | public func newObject(_ value: Set) -> TclObj { return TclObj(value, Interp: self) } 400 | public func newObject(_ value: Set) -> TclObj { return TclObj(value, Interp: self) } 401 | public func newObject(_ value: Set) -> TclObj { return TclObj(value, Interp: self) } 402 | // public func newObject(value: Set) -> TclObj { return TclObj(value, Interp: self) } 403 | public func newObject(_ value: [Int]) -> TclObj { return TclObj(value, Interp: self) } 404 | public func newObject(_ value: [String]) -> TclObj { return TclObj(value, Interp: self) } 405 | public func newObject(_ value: [Double]) -> TclObj { return TclObj(value, Interp: self) } 406 | // public func newObject(value: [Bool]) -> TclObj { return TclObj(value, Interp: self) } 407 | public func newObject(_ value: [String: Int]) -> TclObj { return TclObj(value, Interp: self) } 408 | public func newObject(_ value: [String: String]) -> TclObj { return TclObj(value, Interp: self) } 409 | public func newObject(_ value: [String: Double]) -> TclObj { return TclObj(value, Interp: self) } 410 | // public func newObject(value: [String: Bool]) -> TclObj { return TclObj(value, Interp: self) } 411 | 412 | // Wrappers for TclArray 413 | public func newArray(_ name: String) -> TclArray { return TclArray(name, Interp: self) } 414 | public func newArray(_ name: String, namespace: String) -> TclArray { return TclArray(name, Interp: self, namespace: namespace) } 415 | public func newArray(_ name: String, dict: [String: String]) throws -> TclArray { 416 | return try TclArray(name, Interp: self, dict: dict) 417 | } 418 | public func newArray(_ name: String, dict: [String: String], namespace: String) throws -> TclArray { 419 | return try TclArray(name, Interp: self, namespace: namespace, dict: dict) 420 | } 421 | public func newArray(_ name: String, dict: [String: TclObj]) throws -> TclArray { 422 | return try TclArray(name, Interp: self, dict: dict) 423 | } 424 | public func newArray(_ name: String, dict: [String: TclObj], namespace: String) throws -> TclArray { 425 | return try TclArray(name, Interp: self, namespace: namespace, dict: dict) 426 | } 427 | public func newArray(_ name: String, string: String) throws -> TclArray { 428 | return try TclArray(name, Interp: self, string: string) 429 | } 430 | public func newArray(_ name: String, string: String, namespace: String) throws -> TclArray { 431 | return try TclArray(name, Interp: self, namespace: namespace, string: string) 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is Swift Tcl, a bridge between Swift and Tcl, providing deep interoperability between the two languages. 4 | 5 | Swift developers can use Tcl to make new and powerful ways to interact with, debug, construct automated testing for, and orchestrate the high level activities of their applications. 6 | 7 | Tcl developers can use Swift to gets its sweet, expressive, high performance, scripting-language-like capabilities wherever they would like while retaining all of their existing code. 8 | 9 | Either can go in for a lot or a little. 10 | 11 | Developers can extend Tcl by writing new commands in Swift and extend Swift by writing new commands in Tcl. Tcl commands written in Swift are tiny and simple compared to those written in C. They are a joy. 12 | 13 | Soon, the bridge will be able to automatically generate Tcl interfaces to existing Swift functions and classes through automated scanning of Swift source. 14 | 15 | Likewise through introspection, automation and a bit of static hinting, Tcl procedures appear as native Swift functions, with argument names, default values and "normal" native Swift argument and return data types. 16 | 17 | Users of either language invoke functions written in the other indistinguishably from those written in the one they're using. 18 | 19 | Likewise errors are handled naturally across both languages in both directions. Errors thrown from Swift code called by Tcl come back as Tcl errors with proper errorInfo, errorCode, etc. Closing the loop, uncaught errors occuring in Tcl code called from Swift are thrown back to the caller as Swift errors. 20 | 21 | Swift Tcl defines a *TclInterp* class in Swift that provides methods for creating and managing Tcl interpreters, executing Tcl code in them, etc. Creating a Tcl interpreter and doing something with it in Swift is as easy as: 22 | 23 | ```swift 24 | let interp = TclInterp() 25 | 26 | interp.rawEval("puts {Hello, World.}") 27 | ``` 28 | 29 | It also defines a *TclObj* class that can convert between Swift data types such as Int, Double, String, Swift Arrays, Sets, and Dictionaries and Tcl object representations of equivalent types. 30 | 31 | New Tcl commands written in Swift and are far more compact and generally simpler than their counterparts in C. 32 | 33 | Real work can be done with the Tcl interpeter without using TclObj objects at all. 34 | 35 | The TclInterp object has methods for accessing and manipulating variables, arrays, evaluating code, etc. In the examples below a String and a Double are obtained from variables in the Tcl interpreter: 36 | 37 | ```swift 38 | var autoPath: String = try interp.get(variable: "auto_path") 39 | print("auto_path is '\(autoPath)'") 40 | 41 | let tclVersion: Double = try interp.get(variable: "tcl_version") 42 | print("Tcl version is \(tclVersion)") 43 | ``` 44 | 45 | Y'see how get just gave you the data type you asked for with no funny business? How cool is that? Also note how we use *try* because get will fail if the variable doesn't exist. 46 | 47 | Likewise TclInterp's get can fetch elements out of an array: 48 | 49 | ```swift 50 | var machine: String = try interp.get(variable: "tcl_platform", element: "machine") 51 | ``` 52 | 53 | In this example we import Tcl's global _tcl_platform_ array into a Swift dictionary: 54 | 55 | ```swift 56 | try interp.rawEval("array get tcl_platform") 57 | var dict: [String:String] = try interp.resultObj.toDictionary() 58 | ``` 59 | 60 | We can then access the imported Tcl array in the usual Swift way: 61 | 62 | ```swift 63 | print("Your OS is \(dict["os"]!), running version \(dict["osVersion"]!)") 64 | ``` 65 | 66 | ## Writing Tcl extensions in Swift 67 | 68 | You can extend Tcl with new commands written in Swift. 69 | 70 | Swift functions implementing Tcl commands are invoked with arguments comprising a TclInterp object and an array of TclObj objects corresponding to the arguments to the function (although unlike with C, objv[0] is the first argument to the function, not the function itself.) 71 | 72 | Below is a function that will average all of its arguments that are numbers and return the result: 73 | 74 | To report errors back to Tcl, Swift functions throw them, and because of this and due to included support your function can directly return a String, Int, Double, Bool, TclObj or TclReturn without all that tedious mucking about with the interpreter result and distinct explicit return of a Tcl return code (ok, return, break, continue, error). 75 | 76 | ```swift 77 | func avg (interp: TclInterp, objv: [TclObj]) -> Double { 78 | var sum = 0.0 79 | for obj in objv { 80 | guard let val = obj.doubleValue else {continue} 81 | sum += val 82 | } 83 | return(sum / Double(objv.count)) 84 | } 85 | 86 | interp.createCommand(named: "avg", using: avg) 87 | ``` 88 | 89 | Errors trying to convert Tcl objects to a data type such as trying to convert an alphanumeric string to a Double are thrown by the underlying helper functions and caught by Swift Tcl if you don't catch them in your Swift code. You get nice native error messages. 90 | 91 | TclObj methods like *getDoubleArg* make it a bit easier by tacking on appropriate bits to Tcl's *errorInfo* traceback, making error messages from Swift conformant to the Tcl style. 92 | 93 | ```swift 94 | func fa_latlongs_to_distance_cmd (interp: TclInterp, objv: [TclObj]) throws -> Double { 95 | if (objv.count != 4) { 96 | throw TclError.WrongNumArgs(nLeadingArguments: 0, message: "lat0 lon0 lat1 lon1") 97 | } 98 | 99 | let lat1 = try objv[0].getDoubleArg("lat1") 100 | let lon1 = try objv[1].getDoubleArg("lon1") 101 | let lat2 = try objv[2].getDoubleArg("lat2") 102 | let lon2 = try objv[3].getDoubleArg("lon2") 103 | 104 | let distance = fa_latlongs_to_distance(lat1, lon1: lon1, lat2: lat2, lon2: lon2) 105 | return distance 106 | } 107 | ``` 108 | 109 | In the above example invoking `try interp.rawEval("puts \"distance from KIAH to KSEA is [fa_latlongs_to_distance 29.9844444 -95.3414444 47.4498889 -122.3117778]\"")` emits **distance from KIAH to KSEA is 1874.5897193432174** while `try interp.rawEval("puts \"distance from KIAH to KSEA is [fa_latlongs_to_distance 29.9844444 -95.3414444 crash -122.3117778]\"")` produces a Tcl traceback that looks like 110 | 111 | ``` 112 | expected floating-point number but got "crash" while converting "lat2" argument 113 | invoked from within 114 | "fa_latlongs_to_distance 29.9844444 -95.3414444 crash -122.3117778" 115 | invoked from within 116 | "puts "distance from KIAH to KSEA is [fa_latlongs_to_distance 29.9844444 -95.3414444 crash -122.3117778]"" 117 | ``` 118 | 119 | ## Methods of the TclInterp class 120 | 121 | * `var interp = TclInterp()` 122 | 123 | Create a new Tcl interpreter. You can create as many as you like. 124 | 125 | * `try interp.eval(code: String) -> Type` 126 | 127 | Evaluate Tcl code in the Tcl interpreter. Return value is the result of the code, can be any of String, Int, Double, or Bool. 128 | 129 | * `try interp.RawEval(code: String)` 130 | 131 | Evaluate the code, don't return anything. 132 | 133 | * `var tclList: String = interp.list(list: [String])` 134 | 135 | Safely create a Tcl list from an array of Swift strings. 136 | 137 | * `try interp.RawEval(list: [String])` 138 | 139 | Safer version of rawEval that accepts a string array and converts it to a Tcl list before evaluating it, avoiding potential issues from trying to quote Tcl code ad-hoc in swift. 140 | 141 | You can control whether or not error are printed by manipulating the *printErrors* variable, which currently defaults to true and will include the traceback. 142 | 143 | ### Accessing the interpreter result. 144 | 145 | There are a number of interfaces here, which will be trimmed down with experience: 146 | 147 | * `var result: String = interp.result` 148 | 149 | Obtain the interpreter result as a string. 150 | 151 | * `var resultObj: TclObj = interp.resultObj` 152 | 153 | Obtain the interpreter result as a TclObj object. 154 | 155 | * `interp.result = String` 156 | 157 | Set the interpreter result to be the specified string. 158 | 159 | * `interp.resultObj = TclObj` 160 | 161 | Set the interpreter result to the specified TclObj object. 162 | 163 | * `interp.setResult(Double)` 164 | * `interp.setResult(Int)` 165 | * `interp.setResult(Bool)` 166 | 167 | Set the interpreter result to the specified Double, Int, or Bool, respectively. 168 | 169 | * `interp.getResult() -> String` 170 | * `interp.getResult() -> Double` 171 | * `interp.getResult() -> Int` 172 | * `interp.getResult() -> Bool` 173 | 174 | Get the Interpreter result as the corresponding type. 175 | 176 | ### Registering commands 177 | 178 | * `interp.createCommand(named: String, using: SwiftTclFuncType)` 179 | 180 | Create a new command in the Tcl interpreter with the specified name: when the name is invoked from Tcl the corresponding Swift function will be invoked to perform the command. The Swift function should be of type (tclInterp, [TclObj]) -> Type, where Type can be String, Double, Int, or Bool. Eg: 181 | 182 | * `func swiftFunction (interp: TclInterp, objv: [TclObj]) throws -> Type` 183 | 184 | ### Handling variables. 185 | 186 | * `var val: UnsafeMutablePointer = interp.get(variable: String, element: String?, flags: VariableFlags = [])` 187 | 188 | Get a variable or array element out of the Tcl interpreter and return it as a Tcl\_Obj \*. This is internal and you shouldn't really ever need it. 189 | 190 | * `var val: TclObj = try interp.get(variable: String, element: String?, flags: VariableFlags = [])` 191 | 192 | Get a variable or array element out of the Tcl interpreter and return it as a string or throw an error. 193 | 194 | * `var val: Int = try interp.get(variable: String, element: String?, flags: VariableFlags = [])` 195 | 196 | Get a variable or array element out of the Tcl interpreter and return it as an Int. An error is thrown if the object's contents aren't a valid list or if the element can't be converted to an Int. 197 | 198 | * `var val: Double = try interp.get(variable: String, element: String?, flags: VariableFlags = [])` 199 | * `var val: String = try interp.get(variable: String, element: String?, flags: VariableFlags = [])` 200 | * `var val: Bool = try interp.get(variable: String, element: String?, flags: VariableFlags = [])` 201 | 202 | Get a variable or array element out of the Tcl interpeter and return it as a Double, String or Bool et al or throw an error. 203 | 204 | * `interp.set(variable: varName: String, element: String?, value: String, flags: VariableFlags = [])` 205 | * `interp.set(variable: varName: String, element: String?, value: Int, flags: VariableFlags = [])` 206 | * `interp.set(variable: varName: String, element: String?, value: Double, flags: VariableFlags = [])` 207 | * `interp.set(variable: varName: String, element: String?, value: Bool, flags: VariableFlags = [])` 208 | * `interp.set(variable: varName: String, element: String?, value: TclObj, flags: VariableFlags = [])` 209 | 210 | Set a variable or array element in the Tcl interpeter to be the String, Int, Double, Bool or TclObj that was passed or throw an error if unable. For instance you might be unable to set an array element when the variable name is a scalar. 211 | 212 | * `interp.set (array: String, from: [String: String], flags: VariableFlags = [])` 213 | * `interp.set (array: String, from: [String: Int], flags: VariableFlags = [])` 214 | * `interp.set (array: String, from: [String: Double], flags: VariableFlags = [])` 215 | 216 | Import a Swift Dictionary into a Tcl array. 217 | 218 | Flags are an OptionSet. Values are: 219 | * `.GlobalOnly = TCL_GLOBAL_ONLY` 220 | * `.NamespaceOnly = TCL_NAMESPACE_ONLY` 221 | * `.LeaveErroMsg = TCL_LEAVE_ERR_MSG` 222 | * `.AppendValue = TCL_APPEND_VALUE` 223 | * `.ListElement = TCL_LIST_ELEMENT` 224 | * `.TraceReads = TCL_TRACE_READS` 225 | * `.TraceWrites = TCL_TRACE_WRITES` 226 | * `.TraceUnsets = TCL_TRACE_UNSETS` 227 | * `.TraceDestroyed = TCL_TRACE_DESTROYED` 228 | * `.InterpDestroyed = TCL_INTERP_DESTROYED` 229 | * `.TraceArray = TCL_TRACE_ARRAY` 230 | * `.TraceResultDynamic = TCL_TRACE_RESULT_DYNAMIC` 231 | * `.TraceResultObject = TCL_TRACE_RESULT_OBJECT` 232 | 233 | ###String substitution 234 | 235 | * `interp.subst (String, flags: SubstFlags) -> String` 236 | * `interp.subst (TclObj, flags: SubstFlags) -> TclObj` 237 | 238 | Perform substitution on String or Tcl object in the fashion of the Tcl *subst* command, performing variable substitution, evaluating square-bracketed stuff as embedded Tcl commands and substituting their result, and performing backslash substitution and return the result. 239 | 240 | Flags are an OptionSet of one or more of [.Commands, .Variables, .Backslashes, .All]. [.All] is the default. 241 | 242 | ### Errors 243 | 244 | * `setErrorCode(val: String) throws` 245 | 246 | Set the Tcl error code 247 | 248 | * `addErrorInfo(message: String)` 249 | 250 | Append a message to the error information 251 | 252 | ## The TclObj class 253 | 254 | The TclObj class gives Swift access to Tcl objects. A TclObj can be wrapped around any C-level Tcl Object (Tcl\_Obj \*) and its methods use to access and manipulate the object. 255 | 256 | The object can be new or existing. For example `var obj = interp.newObj(5)` creates a new Tcl object with an integer representation and a value of 5 while `var obj = interp.resultObj` wraps an existing Tcl\_Obj object as a Swift TclObj. 257 | 258 | The TclObj object manages Tcl reference counts so that all this will work. For example, setting a Tcl array element to a TclObj using `interp.set(variable: arrayName, element: element, value: obj)`, the element will continue to hold the object even if the TclObj is deleted on the Swift side. 259 | 260 | * `var obj = interp.newObj()` 261 | * `var obj = interp.newObj(String)` 262 | * `var obj = interp.newObj(Int)` 263 | * `var obj = interp.newObj(Double)` 264 | * `var obj = interp.newObj(Bool)` 265 | 266 | Create a Swift TclObj object that's empty, contains a String, an Int, Double or Bool. 267 | 268 | * `var obj = interp.newObj(Set)` 269 | 270 | Create a TclObj containing a Tcl List initialized from a Set 271 | 272 | * `var obj = interp.newObj([String/Int/Double/Bool])` 273 | 274 | Create a TclObj containing a Tcl List initialized from an array. 275 | 276 | * `var obj = interp.newObj([String: String/Int/Double/Bool])` 277 | 278 | Create a TclObj containing a Tcl List initialized from an dict, in [array get] format... a list of key/value pairs. 279 | 280 | * `internal var obj = interp.newObj(UnsafeMutablePointer)` 281 | 282 | Create a TclObj object encapsulating a UnsafeMutablePointer aka a Tcl\_Obj \*. 283 | 284 | There are setter and getter methods for all of the above types: 285 | 286 | * `obj.set(String)` 287 | * `obj.set(Int)` 288 | * `obj.set(Double)` 289 | * `obj.set(Bool)` 290 | * `obj.set(Set)` 291 | * `obj.set(Set)` 292 | * `obj.set(Set)` 293 | * `obj.set([String])` 294 | * `obj.set([Int])` 295 | * `obj.set([Double])` 296 | * `obj.set([String:String])` 297 | * `obj.set([String:Int])` 298 | * `obj.set([String:Double])` 299 | 300 | Assign TclObj to contain a String, Int, Double or Bool. 301 | 302 | * `var val: String = obj.get()` 303 | 304 | Set String to contain the String representation of whatever TclObj has in it 305 | 306 | * `var val: Int = try obj.get()` 307 | 308 | Set val to contain the Int representation of the TclObj, or throws an error if object cannot be represented as an Int. 309 | 310 | * `var val: Double = try obj.get()` 311 | * `var val: Bool = try obj.get()` 312 | 313 | Same as the above but for Double and Bool. 314 | 315 | * `var val: Set = try obj.get()` 316 | * `var val: [String/Int/Double/Bool] = try obj.get()` 317 | * `var val: [String: String/Int/Double/Bool] = try obj.get()` 318 | 319 | Same as above for sets, arrays, and dicts. 320 | 321 | * `var nativeObj: UnsafeMutablePointer = obj.get()` 322 | 323 | Obtain a pointer to the native C Tcl object from a TclObj 324 | 325 | * `try obj.lappend(value: UnsafeMootablePointer)` 326 | 327 | Append a Tcl\_Obj \* to a list contained in a TclObj. 328 | 329 | * `try obj.lappend(value: Int)` 330 | * `try obj.lappend(value: Double)` 331 | * `try obj.lappend(value: String)` 332 | * `try obj.lappend(value: Bool)` 333 | * `try obj.lappend(value: TclObj)` 334 | 335 | Append an Int, Double, String, Bool or TclObj to a list contained in a TclObj. 336 | 337 | * `try obj.lappend(array: [Int])` 338 | * `try obj.lappend(array: [Double])` 339 | * `try obj.lappend(array: [String])` 340 | 341 | Append an array of Int, Double, or String to a list contained in a TclObj. Each element is appended. 342 | 343 | * `var val: Int = try obj.lindex(index)` 344 | * `var val: Double = try obj.lindex(index)` 345 | * `var val: String = try obj.lindex(index)` 346 | * `var val: Bool = try obj.lindex(index)` 347 | * `var val: TclObj = try obj.lindex(index)` 348 | 349 | Return the nth element of the obj as a list, if possible, according to the specified data type, else throws an error. 350 | 351 | * `var val: [Int] = try obj.lrange(start...end)` 352 | * `var val: [Double] = try obj.lrange(start...end)` 353 | * `var val: [String] = try obj.lrange(start...end)` 354 | * `var val: [Bool] = try obj.lrange(start...end)` 355 | 356 | Return the start...end range of object as a list 357 | 358 | * `try obj.linsert(index, list: [TclObj])` 359 | * `try obj.linsert(index, list: [String])` 360 | * `try obj.linsert(index, list: [Int])` 361 | * `try obj.linsert(index, list: [Double])` 362 | * `try obj.linsert(index, list: [Bool])` 363 | 364 | Insert the array into the object, before index. 365 | 366 | * `try obj.linsert(start...end, list: [TclObj])` 367 | * `try obj.linsert(start...end, list: [String])` 368 | * `try obj.linsert(start...end, list: [Int])` 369 | * `try obj.linsert(start...end, list: [Double])` 370 | * `try obj.linsert(start...end, list: [Bool])` 371 | 372 | Insert the array into the object, replacing the specified range. 373 | 374 | * `var count: Int = try obj.llength()` 375 | 376 | Return the number of elements in the list contained in the TclObj or throws an error if the value in the TclObj cannot be represented as a list. 377 | 378 | * `var s: TclObj? = obj[index]` 379 | * `var s: String? = obj[index]` 380 | * `var s: [TclObj]? = obj[start...end]` 381 | * `var s: [String]? = obj[start...end]` 382 | 383 | Subscripting the object treats it as a list, exactly like lindex and lrange. 384 | 385 | * `obj[index] = String/Int/Double/Bool` 386 | * `obj[start...end] = [String/Int/Double/Bool]` 387 | 388 | And you can set elements in the object, exactly like linsert. 389 | 390 | Finally, a list is a sequence. Unfortunately, Swift doesn't support generic protocols, so it can only generate one type. Right now it's a sequence of TclObj: 391 | 392 | ```swift 393 | var intlist = interp.newObj([1, 2, 3, 4]) 394 | for element in intlist { 395 | if let i: Int = try? element.get() { 396 | print("Got '\(i)'") 397 | } 398 | } 399 | ``` 400 | 401 | ## The TclArray class 402 | 403 | The TclArray convenience class gives Swift access to Tcl arrays. A TclArray encapsulates an interpreter and an array name. 404 | 405 | * `var a = interp.newArray(name: String)` 406 | * `var a = interp.newArray(name: String, dict: [String: String])` 407 | * `var a = interp.newArray(name: String, dict: [String: TclObj])` 408 | * `var a = interp.newArray(name: String, string: String)` 409 | 410 | A TclArray can be converted into or out of a Swift dictionary. 411 | 412 | * `var d: [String: String] = array.get()` 413 | * `var d: [String: TclObj] = array.get()` 414 | * `array.set(d)` 415 | 416 | Members of the array can be extracted or modified: 417 | 418 | * `var s: String = array["name"]` 419 | * `array["name"] = "new value"` 420 | 421 | The array is a sequence of (String, TclObj): 422 | 423 | ```swift 424 | for (name, value) in array { 425 | try print("array(\(name)) = \(value.get() as String)") 426 | } 427 | ``` 428 | 429 | ## Building 430 | 431 | Right now this is on the Mac and requires Xcode and the included Xcode project looks to Tcl as installed by macports (https://www.macports.org/) with the include file in /opt/local/include and the tcl dylib in /opt/local/lib. 432 | 433 | To go this way make sure Xcode and macports are installed and install Tcl with "sudo port install tcl". 434 | 435 | The code can also be built against the Tcl that gets installed in /usr/include, /usr/lib, etc, as installed by the Xcode command line tools if you change the targets in the project. 436 | 437 | ## Running it 438 | 439 | I'm currently just running it from within Xcode. The TclInterp and TclObj classes are defined in tcl.swift while main.swift defines a TclInterp and messes around with it a bit. 440 | 441 | ## Stuff it has 442 | 443 | * A Swift TclInterp that wraps Tcl interpreters and provides methods to do stuff with them such as evaluate Tcl code, get and set the Interpreter result, etc. 444 | * A way to create new Tcl commands that invoke Swift functions 445 | * A Swift TclObj that wraps TclObj's and provides methods to do stuff with them 446 | * Methods to convert between Swift Lists, Sets and Dictionaries and Tcl lists, arrays and dicts 447 | * Additional methods of the TclInterp class to get and set Tcl variables (including array elements) and additional ways to interact with the Tcl interpreter that are useful 448 | 449 | ## Stuff it needs 450 | 451 | * proc aliasing (generated scaffolding makes procs indistinguishable from other Swift functions) 452 | * tcl object system class aliasing 453 | 454 | ## Stuff that would be nice eventually 455 | 456 | * Support for safe interpreters 457 | * Support for setting resource limits on interpreters using Tcl\_Limit\* 458 | 459 | ## Stuff that would be cool if it's even possible 460 | 461 | * The ability to introspect Swift from Tcl to find objects and invoke methods on them (Swift seems to be moving away from even providing a way to do that. I still think bridging is useful even if it can't do this.) 462 | 463 | ## Stuff that might be interesting to try 464 | 465 | * linking Tcl variables to Swift variables using Tcl_LinkVar 466 | * Shadowing Tcl arrays in Swift 467 | * Tcl dictionary interface 468 | * lrange and stuff 469 | * automatically generating interfaces between The [TclObj] call trampoline and functions with native types 470 | -------------------------------------------------------------------------------- /Sources/tcl-object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // tcl-object.swift 3 | // tcl-swift-bridge 4 | // 5 | // Created by Peter da Silva on 5/17/16. 6 | // Copyright © 2016 FlightAware. All rights reserved. 7 | // 8 | // Free under the Berkeley license. 9 | // 10 | 11 | import Foundation 12 | import Tcl8_6 13 | 14 | 15 | // TclObj - Tcl object class 16 | 17 | public class TclObj: Sequence { 18 | let obj: UnsafeMutablePointer 19 | let Interp: TclInterp 20 | let interp: UnsafeMutablePointer 21 | 22 | // various initializers to create a Tcl object from nothing, an int, 23 | // double, string, Tcl_Obj *, etc 24 | 25 | // init - Initialize from a Tcl_Obj * 26 | init(_ val: UnsafeMutablePointer, Interp: TclInterp) { 27 | self.Interp = Interp; self.interp = Interp.interp 28 | obj = val 29 | IncrRefCount(val) 30 | } 31 | 32 | // init - initialize from nothing, get an empty Tcl object 33 | public convenience init(Interp: TclInterp) { 34 | self.init(Tcl_NewObj(), Interp: Interp) 35 | } 36 | 37 | // init - initialize from a Swift Int 38 | public convenience init(_ val: Int, Interp: TclInterp) { 39 | self.init(Interp: Interp) 40 | self.set(val); 41 | } 42 | 43 | // init - initialize from a Swift String 44 | public convenience init(_ val: String, Interp: TclInterp) { 45 | self.init(Interp: Interp) 46 | self.set(val); 47 | } 48 | 49 | // init - initialize from a Swift Double 50 | public convenience init(_ val: Double, Interp: TclInterp) { 51 | self.init(Interp: Interp) 52 | self.set(val); 53 | } 54 | 55 | // init - initialize from a Swift Bool 56 | public convenience init(_ val: Bool, Interp: TclInterp) { 57 | self.init(Interp: Interp) 58 | self.set(val); 59 | } 60 | 61 | // init - init from a set of Strings to a list 62 | public convenience init(_ set: Set, Interp: TclInterp) { 63 | self.init(Interp: Interp) 64 | self.set(set) 65 | } 66 | 67 | public func set(_ set: Set) { 68 | for element in set { 69 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewStringObj (element, -1)) 70 | } 71 | } 72 | 73 | // init from a set of Ints to a list 74 | public convenience init(_ set: Set, Interp: TclInterp) { 75 | self.init(Interp: Interp) 76 | self.set(set) 77 | } 78 | 79 | public func set(_ set: Set) { 80 | for element in set { 81 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewLongObj (element)) 82 | } 83 | } 84 | 85 | // init from a Set of doubles to a list 86 | public convenience init(_ set: Set, Interp: TclInterp) { 87 | self.init(Interp: Interp) 88 | self.set(set) 89 | } 90 | 91 | public func set(_ set: Set) { 92 | for element in set { 93 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewDoubleObj (element)) 94 | } 95 | } 96 | 97 | // init from an Array of Strings to a Tcl list 98 | public convenience init(_ array: [String], Interp: TclInterp) { 99 | self.init(Interp: Interp) 100 | self.set(array) 101 | } 102 | 103 | public func set(_ array: [String]) { 104 | for element in array { 105 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewStringObj (element, -1)) 106 | } 107 | } 108 | 109 | // Init from an Array of Int to a Tcl list 110 | public convenience init (_ array: [Int], Interp: TclInterp) { 111 | self.init(Interp: Interp) 112 | self.set(array) 113 | } 114 | 115 | public func set(_ array: [Int]) { 116 | for element in array { 117 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewLongObj(element)) 118 | } 119 | } 120 | 121 | // Init from an Array of Double to a Tcl list 122 | public convenience init (_ array: [Double], Interp: TclInterp) { 123 | self.init(Interp: Interp) 124 | self.set(array) 125 | } 126 | 127 | public func set(_ array: [Double]) { 128 | for element in array { 129 | Tcl_ListObjAppendElement(interp, obj, Tcl_NewDoubleObj(element)) 130 | } 131 | } 132 | 133 | // init from a String/String dictionary to a list 134 | public convenience init (_ dictionary: [String: String], Interp: TclInterp) { 135 | self.init(Interp: Interp) 136 | self.set(dictionary) 137 | } 138 | 139 | public func set(_ dictionary: [String: String]) { 140 | for (key, val) in dictionary { 141 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewStringObj (key, -1)) 142 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewStringObj (val, -1)) 143 | } 144 | } 145 | 146 | // init from a String/Int dictionary to a list 147 | public convenience init (_ dictionary: [String: Int], Interp: TclInterp) { 148 | self.init(Interp: Interp) 149 | self.set(dictionary) 150 | } 151 | 152 | public func set(_ dictionary: [String: Int]) { 153 | for (key, val) in dictionary { 154 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewStringObj (key, -1)) 155 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewLongObj (val)) 156 | } 157 | } 158 | 159 | // init from a String/Double dictionary to a list 160 | public convenience init (_ dictionary: [String: Double], Interp: TclInterp) { 161 | self.init(Interp: Interp) 162 | self.set(dictionary) 163 | } 164 | 165 | public func set(_ dictionary: [String: Double]) { 166 | for (key, val) in dictionary { 167 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewStringObj (key, -1)) 168 | Tcl_ListObjAppendElement (interp, obj, Tcl_NewDoubleObj (val)) 169 | } 170 | } 171 | 172 | // deinit - decrement the object's reference count. if it goes below one 173 | // the object will be freed. if not then something else has it and it will 174 | // be freed after the last use 175 | deinit { 176 | DecrRefCount(obj) 177 | } 178 | 179 | // various set functions to set the Tcl object from a string, Int, Double, etc 180 | public var stringValue: String? { 181 | get { 182 | return try? get() 183 | } 184 | set { 185 | guard let val = newValue else {return} 186 | set(val) 187 | } 188 | } 189 | 190 | public func get() throws -> String { 191 | return try tclobjp_to_String(obj) 192 | } 193 | 194 | public func set(_ value: String) { 195 | Tcl_SetStringObj (obj, value, -1) 196 | } 197 | 198 | // getInt - return the Tcl object as an Int or nil 199 | // if in-object Tcl type conversion fails 200 | public var intValue: Int? { 201 | get { 202 | return try? get() 203 | } 204 | set { 205 | guard let val = newValue else {return} 206 | set(val) 207 | } 208 | } 209 | 210 | public func get() throws -> Int { 211 | return try tclobjp_to_Int(obj) 212 | } 213 | 214 | public func set(_ val: Int) { 215 | Tcl_SetLongObj (obj, val) 216 | } 217 | 218 | // getDouble - return the Tcl object as a Double or nil 219 | // if in-object Tcl type conversion fails 220 | public var doubleValue: Double? { 221 | get { 222 | return try? get() 223 | } 224 | set { 225 | guard let val = newValue else {return} 226 | set(val) 227 | } 228 | } 229 | 230 | public func get() throws -> Double { 231 | return try tclobjp_to_Double(obj) 232 | } 233 | 234 | public func set(_ val: Double) { 235 | Tcl_SetDoubleObj (obj, val) 236 | } 237 | 238 | // getBool - return the Tcl object as a Bool or nil 239 | public var boolValue: Bool? { 240 | get { 241 | return try? get() 242 | } 243 | set { 244 | guard let val = newValue else {return} 245 | set(val) 246 | } 247 | } 248 | 249 | public func get() throws -> Bool { 250 | return try tclobjp_to_Bool(obj) 251 | } 252 | 253 | public func set(_ val: Bool) { 254 | Tcl_SetBooleanObj (obj, val ? 1 : 0) 255 | } 256 | 257 | // getObj - return the Tcl object pointer (Tcl_Obj *) 258 | public func get() -> UnsafeMutablePointer { 259 | return obj 260 | } 261 | 262 | public func getAsArg(named varName: String) throws -> Int { 263 | do { 264 | return try tclobjp_to_Int(obj, interp: interp) 265 | } catch { 266 | Interp.addErrorInfo(" while converting \"\(varName)\" argument") 267 | throw TclError.error 268 | } 269 | } 270 | 271 | public func getAsArg(named varName: String) throws -> Double { 272 | do { 273 | return try tclobjp_to_Double(obj, interp: interp) 274 | } catch { 275 | Interp.addErrorInfo(" while converting \"\(varName)\" argument") 276 | throw TclError.error 277 | } 278 | } 279 | 280 | public func getAsArg(named varName: String) throws -> Bool { 281 | do { 282 | return try tclobjp_to_Bool(obj, interp: interp) 283 | } catch { 284 | Interp.addErrorInfo(" while converting \"\(varName)\" argument") 285 | throw TclError.error 286 | } 287 | } 288 | 289 | public func getAsArg(named varName: String) throws -> String { 290 | do { 291 | return try tclobjp_to_String(obj) 292 | } catch { 293 | Interp.addErrorInfo(" while converting \"\(varName)\" argument") 294 | throw TclError.error 295 | } 296 | } 297 | 298 | // lappend - append a Tcl_Obj * to the Tcl object list 299 | func lappend (_ value: UnsafeMutablePointer) throws { 300 | guard (Tcl_ListObjAppendElement (interp, obj, value) != TCL_ERROR) else {throw TclError.error} 301 | } 302 | 303 | // lappend - append an Int to the Tcl object list 304 | public func lappend (_ value: Int) throws { 305 | try self.lappend (Tcl_NewLongObj (value)) 306 | } 307 | 308 | // lappend - append a Double to the Tcl object list 309 | public func lappend (_ value: Double) throws { 310 | try self.lappend (Tcl_NewDoubleObj (value)) 311 | } 312 | 313 | // lappend - append a String to the Tcl object list 314 | public func lappend (_ value: String) throws { 315 | try self.lappend(Tcl_NewStringObj (value, -1)) 316 | } 317 | 318 | // lappend - append a Bool to the Tcl object list 319 | public func lappend (_ value: Bool) throws { 320 | try self.lappend (Tcl_NewBooleanObj (value ? 1 : 0)) 321 | } 322 | 323 | // lappend - append a tclObj to the Tcl object list 324 | public func lappend (_ value: TclObj) throws { 325 | try self.lappend(value) 326 | } 327 | 328 | // lappend - append an array of Int to the Tcl object list 329 | // (flattens them out) 330 | public func lappend (_ array: [Int]) throws { 331 | for element in array { 332 | try self.lappend(element) 333 | } 334 | } 335 | 336 | // lappend - append an array of Double to the Tcl object list 337 | // (flattens them out) 338 | public func lappend (_ array: [Double]) throws { 339 | for element in array { 340 | try self.lappend(element) 341 | } 342 | } 343 | 344 | // lappend - append an array of String to the Tcl object list 345 | // (flattens them out) 346 | public func lappend (_ array: [String]) throws { 347 | for element in array { 348 | try self.lappend(element) 349 | } 350 | } 351 | 352 | // llength - return the number of elements in the list if the contents of our obj can be interpreted as a list 353 | public func llength () throws -> Int { 354 | var count: Int32 = 0 355 | if (Tcl_ListObjLength(interp, obj, &count) == TCL_ERROR) { 356 | throw TclError.error 357 | } 358 | return Int(count) 359 | } 360 | 361 | // lindex - return the nth element treating obj as a list, if possible, and return a Tcl_Obj * 362 | func lindex (_ index: Int) throws -> UnsafeMutablePointer? { 363 | var tmpObj: UnsafeMutablePointer? = nil 364 | var index = index; 365 | if(index < 0) { 366 | if let count = try? self.llength() { 367 | index += count 368 | } 369 | } 370 | if Tcl_ListObjIndex(interp, obj, Int32(index), &tmpObj) == TCL_ERROR { 371 | throw TclError.error 372 | } 373 | return tmpObj 374 | } 375 | 376 | // lindex returning a TclObj object or nil 377 | public func lindex (_ index: Int) throws -> TclObj? { 378 | let tmpObj: UnsafeMutablePointer? = try self.lindex(index) 379 | return TclObj(tmpObj!, Interp: Interp) 380 | } 381 | 382 | // lindex returning an Int or nil 383 | public func lindex (_ index: Int) throws -> Int { 384 | let tmpObj: UnsafeMutablePointer? = try self.lindex(index) 385 | 386 | return try tclobjp_to_Int(tmpObj, interp: interp) 387 | } 388 | 389 | // lindex returning a Double or nil 390 | public func lindex (_ index: Int) throws -> Double { 391 | let tmpObj: UnsafeMutablePointer? = try self.lindex(index) 392 | 393 | return try tclobjp_to_Double(tmpObj, interp: interp) 394 | } 395 | 396 | // lindex returning a String or nil 397 | public func lindex (_ index: Int) throws -> String { 398 | let tmpObj: UnsafeMutablePointer? = try self.lindex(index) 399 | 400 | return try tclobjp_to_String(tmpObj) 401 | } 402 | 403 | // lindex returning a Bool or nil 404 | public func lindex (_ index: Int) throws -> Bool { 405 | let tmpObj: UnsafeMutablePointer? = try self.lindex(index) 406 | 407 | return try tclobjp_to_Bool(tmpObj, interp: interp) 408 | } 409 | 410 | // toDictionary - copy the tcl object as a list into a String/TclObj dictionary 411 | public func get() throws -> [String: TclObj] { 412 | var dictionary: [String: TclObj] = [:] 413 | 414 | var objc: Int32 = 0 415 | var objv: UnsafeMutablePointer?>? = nil 416 | 417 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 418 | 419 | for i in stride(from: 0, to: Int(objc)-1, by: 2) { 420 | let keyString = try tclobjp_to_String(objv![i]) 421 | dictionary[keyString] = TclObj(objv![i+1]!, Interp: Interp) 422 | } 423 | return dictionary 424 | } 425 | 426 | // toArray - create a String array from the tcl object as a list 427 | public func get() throws -> [String] { 428 | var array: [String] = [] 429 | 430 | var objc: Int32 = 0 431 | var objv: UnsafeMutablePointer?>? = nil 432 | 433 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 434 | 435 | for i in 0.. [Int] { 444 | var array: [Int] = [] 445 | 446 | var objc: Int32 = 0 447 | var objv: UnsafeMutablePointer?>? = nil 448 | 449 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 450 | 451 | for i in 0.. [Double] { 461 | var array: [Double] = [] 462 | 463 | var objc: Int32 = 0 464 | var objv: UnsafeMutablePointer?>? = nil 465 | 466 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 467 | 468 | for i in 0.. [TclObj] { 481 | var array: [TclObj] = [] 482 | 483 | var objc: Int32 = 0 484 | var objv: UnsafeMutablePointer?>? = nil 485 | 486 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 487 | 488 | for i in 0.. ( Int, Int) { 497 | var start: Int = first 498 | var end: Int = last 499 | 500 | if start < 0 { start = Swift.max(0, count + start) } 501 | else if start >= count { start = count - 1 } 502 | 503 | if end < 0 { end = Swift.max(0, count + end) } 504 | else if end >= count { end = count - 1} 505 | 506 | if end < start { end = start } 507 | 508 | return (start, end) 509 | } 510 | 511 | // lrange returning a TclObj array 512 | public func lrange (_ range: CountableClosedRange) throws -> [TclObj] { 513 | var array: [TclObj] = [] 514 | 515 | var objc: Int32 = 0 516 | var objv: UnsafeMutablePointer?>? = nil 517 | 518 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 519 | 520 | let (start, end) = normalize_range(range.lowerBound, range.upperBound-1, Int(objc)) 521 | 522 | for i in start...end { 523 | array.append(TclObj((objv?[i])!, Interp: Interp)) 524 | } 525 | 526 | return array 527 | } 528 | 529 | // lrange returning a string array 530 | public func lrange (_ range: CountableClosedRange) throws -> [String] { 531 | var array: [String] = [] 532 | 533 | var objc: Int32 = 0 534 | var objv: UnsafeMutablePointer?>? = nil 535 | 536 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 537 | 538 | let (start, end) = normalize_range(range.lowerBound, range.upperBound-1, Int(objc)) 539 | 540 | for i in start...end { 541 | try array.append(tclobjp_to_String(objv![i])) 542 | } 543 | 544 | return array 545 | } 546 | 547 | // lrange returning an integer array 548 | public func lrange (_ range: CountableClosedRange) throws -> [Int] { 549 | var array: [Int] = [] 550 | 551 | var objc: Int32 = 0 552 | var objv: UnsafeMutablePointer?>? = nil 553 | 554 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 555 | 556 | let (start, end) = normalize_range(range.lowerBound, range.upperBound - 1, Int(objc)) 557 | 558 | for i in start...end { 559 | let longVal = try tclobjp_to_Int(objv![i], interp: interp) 560 | array.append(longVal) 561 | } 562 | 563 | return array 564 | } 565 | 566 | // lrange returning a float array 567 | public func lrange (_ range: CountableClosedRange) throws -> [Double] { 568 | var array: [Double] = [] 569 | 570 | var objc: Int32 = 0 571 | var objv: UnsafeMutablePointer?>? = nil 572 | 573 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 574 | 575 | let (start, end) = normalize_range(range.lowerBound, range.upperBound - 1, Int(objc)) 576 | 577 | for i in start...end { 578 | let doubleVal = try tclobjp_to_Double(objv![i], interp: interp) 579 | array.append(doubleVal) 580 | } 581 | 582 | return array 583 | } 584 | 585 | // lrange returning a boolean array 586 | public func lrange (_ range: CountableClosedRange) throws -> [Bool] { 587 | var array: [Bool] = [] 588 | 589 | var objc: Int32 = 0 590 | var objv: UnsafeMutablePointer?>? = nil 591 | 592 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 593 | 594 | let (start, end) = normalize_range(range.lowerBound, range.upperBound-1, Int(objc)) 595 | 596 | for i in start...end { 597 | let boolVal = try tclobjp_to_Bool(objv![i], interp: interp) 598 | array.append(boolVal) 599 | } 600 | 601 | return array 602 | } 603 | 604 | // get - copy the tcl object as a list into a String/String dictionary 605 | public func get() throws -> [String: String] { 606 | var dictionary: [String: String] = [:] 607 | 608 | var objc: Int32 = 0 609 | var objv: UnsafeMutablePointer?>? = nil 610 | 611 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 612 | 613 | for i in stride(from: 0, to: Int(objc-1), by: 2) { 614 | let keyString = try tclobjp_to_String(objv![i]) 615 | let valueString = try tclobjp_to_String(objv![i+1]) 616 | 617 | dictionary[keyString] = valueString 618 | } 619 | return dictionary 620 | } 621 | 622 | // get - copy the tcl object as a list into a String/Int dictionary 623 | public func get() throws -> [String: Int] { 624 | var dictionary: [String: Int] = [:] 625 | 626 | var objc: Int32 = 0 627 | var objv: UnsafeMutablePointer?>? = nil 628 | 629 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 630 | 631 | for i in stride(from: 0, to: Int(objc-1), by: 2) { 632 | let keyString = try tclobjp_to_String(objv![i]) 633 | let val = try tclobjp_to_Int(objv![i+1]) 634 | dictionary[keyString] = val 635 | } 636 | return dictionary 637 | } 638 | 639 | // get - copy the tcl object as a list into a String/Double dictionary 640 | public func get() throws -> [String: Double] { 641 | var dictionary: [String: Double] = [:] 642 | 643 | var objc: Int32 = 0 644 | var objv: UnsafeMutablePointer?>? = nil 645 | 646 | if Tcl_ListObjGetElements(interp, obj, &objc, &objv) == TCL_ERROR {throw TclError.error} 647 | 648 | for i in stride(from: 0, to: Int(objc-1), by: 2) { 649 | let keyString = try tclobjp_to_String(objv![i]) 650 | let val = try tclobjp_to_Double(objv![i+1]) 651 | 652 | dictionary[keyString] = val 653 | } 654 | return dictionary 655 | } 656 | 657 | public subscript(index: Int) -> TclObj? { 658 | get { 659 | if let result : TclObj? = try? self.lindex(index) { 660 | return result 661 | } else { 662 | return nil 663 | } 664 | } 665 | set { 666 | var list: [TclObj] 667 | if let value = newValue { 668 | list = [value] 669 | } else { 670 | list = [] 671 | } 672 | do { try lreplace(index...index, list: list) } catch { } 673 | } 674 | } 675 | 676 | public subscript(range: CountableClosedRange) -> [TclObj]? { 677 | get { 678 | if let result : [TclObj] = try? self.lrange(range) { 679 | return result 680 | } else { 681 | return nil 682 | } 683 | } 684 | set { 685 | var list: [TclObj] 686 | if let value = newValue { 687 | list = value 688 | } else { 689 | list = [] 690 | } 691 | do { try lreplace(range, list: list) } catch { } 692 | } 693 | } 694 | 695 | public subscript(index: Int) -> String? { 696 | get { 697 | if let result : String = try? self.lindex(index) { 698 | return result 699 | } else { 700 | return nil 701 | } 702 | } 703 | set { 704 | var list: [String] 705 | if let value = newValue { 706 | list = [value] 707 | } else { 708 | list = [] 709 | } 710 | do { try lreplace(index...index, list: list) } catch { } 711 | } 712 | } 713 | 714 | public subscript(range: CountableClosedRange) -> [String]? { 715 | get { 716 | if let result : [String] = try? self.lrange(range) { 717 | return result 718 | } else { 719 | return nil 720 | } 721 | } 722 | set { 723 | var list: [String] 724 | if let value = newValue { 725 | list = value 726 | } else { 727 | list = [] 728 | } 729 | do { try lreplace(range, list: list) } catch { } 730 | } 731 | } 732 | 733 | public subscript(index: Int) -> Double? { 734 | get { 735 | if let result : Double = try? self.lindex(index) { 736 | return result 737 | } else { 738 | return nil 739 | } 740 | } 741 | set { 742 | var list: [Double] 743 | if let value = newValue { 744 | list = [value] 745 | } else { 746 | list = [] 747 | } 748 | do { try lreplace(index...index, list: list) } catch { } 749 | } 750 | } 751 | 752 | public subscript(range: CountableClosedRange) -> [Double]? { 753 | get { 754 | if let result : [Double] = try? self.lrange(range) { 755 | return result 756 | } else { 757 | return nil 758 | } 759 | } 760 | set { 761 | var list: [Double] 762 | if let value = newValue { 763 | list = value 764 | } else { 765 | list = [] 766 | } 767 | do { try lreplace(range, list: list) } catch { } 768 | } 769 | } 770 | 771 | public subscript(index: Int) -> Int? { 772 | get { 773 | if let result : Int = try? self.lindex(index) { 774 | return result 775 | } else { 776 | return nil 777 | } 778 | } 779 | set { 780 | var list: [Int] 781 | if let value = newValue { 782 | list = [value] 783 | } else { 784 | list = [] 785 | } 786 | do { try lreplace(index...index, list: list) } catch { } 787 | } 788 | } 789 | 790 | public subscript(range: CountableClosedRange) -> [Int]? { 791 | get { 792 | if let result : [Int] = try? self.lrange(range) { 793 | return result 794 | } else { 795 | return nil 796 | } 797 | } 798 | set { 799 | var list: [Int] 800 | if let value = newValue { 801 | list = value 802 | } else { 803 | list = [] 804 | } 805 | do { try lreplace(range, list: list) } catch { } 806 | } 807 | } 808 | 809 | public subscript(index: Int) -> Bool? { 810 | get { 811 | if let result : Bool = try? self.lindex(index) { 812 | return result 813 | } else { 814 | return nil 815 | } 816 | } 817 | set { 818 | var list: [Bool] 819 | if let value = newValue { 820 | list = [value] 821 | } else { 822 | list = [] 823 | } 824 | do { try lreplace(index...index, list: list) } catch { } 825 | } 826 | } 827 | 828 | public subscript(range: CountableClosedRange) -> [Bool]? { 829 | get { 830 | if let result : [Bool] = try? self.lrange(range) { 831 | return result 832 | } else { 833 | return nil 834 | } 835 | } 836 | set { 837 | var list: [Bool] 838 | if let value = newValue { 839 | list = value 840 | } else { 841 | list = [] 842 | } 843 | do { try lreplace(range, list: list) } catch { } 844 | } 845 | } 846 | 847 | // lreplace(range, list) and variants 848 | func lreplace (_ range: CountableClosedRange, objv: [UnsafeMutablePointer?]) throws { 849 | guard (Tcl_ListObjReplace (interp, obj, Int32(range.lowerBound), Int32(range.upperBound-range.lowerBound), Int32(objv.count), objv) != TCL_ERROR) else { throw TclError.error } 850 | } 851 | 852 | public func lreplace (_ range: CountableClosedRange, list: [TclObj]) throws { 853 | try self.lreplace(range, objv: list.map { $0.obj }) 854 | } 855 | 856 | // IMPORTANT NOTE 857 | // Orginally used self.lreplace(range, objv: list.map { TclObj($0, Interp: Interp).obj } ) 858 | // This allocated and deallocated the TclObj for each step of the map, so passing freed memory to Tcl_ListObjReplace above 859 | // Creating a [ TclObj ] meant that none of the TclObjs are deallocated until lreplace returns. 860 | public func lreplace (_ range: CountableClosedRange, list: [String]) throws { 861 | try self.lreplace(range, list: list.map { TclObj($0, Interp: Interp) }) 862 | } 863 | 864 | public func lreplace (_ range: CountableClosedRange, list: [Int]) throws { 865 | try self.lreplace(range, list: list.map { TclObj($0, Interp: Interp) }) 866 | } 867 | 868 | public func lreplace (_ range: CountableClosedRange, list: [Double]) throws { 869 | try self.lreplace(range, list: list.map { TclObj($0, Interp: Interp) }) 870 | } 871 | 872 | public func lreplace (_ range: CountableClosedRange, list: [Bool]) throws { 873 | try self.lreplace(range, list: list.map { TclObj($0, Interp: Interp) }) 874 | } 875 | 876 | func linsert (_ index: Int, objv: [UnsafeMutablePointer?]) throws { 877 | guard (Tcl_ListObjReplace (interp, obj, Int32(index), Int32(0), Int32(objv.count), objv) != TCL_ERROR) else {throw TclError.error} 878 | } 879 | 880 | public func linsert (_ index: Int, list: [TclObj]) throws { 881 | try self.linsert(index, objv: list.map { $0.obj }) 882 | } 883 | 884 | public func linsert (_ index: Int, list: [String]) throws { 885 | try self.linsert(index, list: list.map { TclObj($0, Interp: Interp) }) 886 | } 887 | 888 | public func linsert (_ index: Int, list: [Int]) throws { 889 | try self.linsert(index, list: list.map { TclObj($0, Interp: Interp) }) 890 | } 891 | 892 | public func linsert (_ index: Int, list: [Double]) throws { 893 | try self.linsert(index, list: list.map { TclObj($0, Interp: Interp) }) 894 | } 895 | 896 | public func linsert (_ index: Int, list: [Bool]) throws { 897 | try self.linsert(index, list: list.map { TclObj($0, Interp: Interp) }) 898 | } 899 | 900 | public func makeIterator() -> AnyIterator { 901 | var next = 0 902 | return AnyIterator { 903 | guard let length = try? self.llength() else { 904 | return nil 905 | } 906 | if next >= length { 907 | return nil 908 | } 909 | guard let element: TclObj? = try? self.lindex(next) else { 910 | return nil 911 | } 912 | next += 1 913 | return element; 914 | } 915 | } 916 | } 917 | --------------------------------------------------------------------------------