Code on this page has been statically highlighted by Ticol
Print "Hello world" | puts "Hello world" |
|
Define a procedure. Store the name in variable s and call by dereferencing s | proc foo {} {puts Hello} set s foo $s |
|
More advanced Hello world by storing the command in q and expanding using the {*} operator | set q "puts {Hello world}" {*}$q |
|
Hello world as a CGI web application | puts "Content-type: text/html\r\n\r\n" puts "Hello world" die |
|
Loop iterate variable i from 0 to 9 and print out Shows the use of: option expression which modifes how flow-control commands handle arguments |
option expression off for {set i 0} {< $i 10} {incr i} { puts "i is $i" ++ i } option expression on for {set i 0} {$i < 10} {incr i} { puts "i is $i" ++ i } |
|
The [foreach] command may often be more useful than [for] | set l {a b c d e f} foreach {x y} $l { puts "Multiple variables may be processed $x $y" } |
|
Multiple commands on one line | set a 22;set b 7.0;set p [expr $a/$b];puts "Pi is $p" |
|
Example of multi-level dereferencing using $
compared to using [set] In Tcl, [set] is used either to assign or to echo a variable's contents Each level of dereference called for [puts] is the same. $var is equivalent to [set var] The variable chain runs: d -> c -> b -> a -> 23 |
set a 23 set b a set c b set d c puts $$$$d # 4 levels of dereference puts [set $$$d] # 4 levels of dereference puts [set [set $$d]] # 4 levels of dereference puts [set [set [set $d]]] # 4 levels of dereference puts [set [set [set [set d]]]] # 4 levels of dereference |
|
Dynamically include data from another file using
[return] The data file returns a list of pairs which can be used by [array set] |
# Data file. include_data.tcl return { 1 {foo} 2 {bar} 3 {baz} 4 {quux} } # Main file which will include the data file above puts "Including include_data.tcl" array set data [source include_data.tcl] for {set i 1} {<= $i [array size data]} {++ i} { puts "$data($i)" } |
|
Read up to 100 bytes from a file referred
to in the first script argument - argv(1) The commands are similar to those available in C/C++ [readfile] is also available, which can read via a single command |
set fp [file open [unescape $argv(1)]] set r [file gets $fp s 100] puts "s is '$s" file close $fp |
|
Call nested levels of [expr] including Tcl commands | puts [expr [<< [expr 20+3] [expr 2^4] 32] - 472] puts [expr 1*[<< 1 1]*3/4.0*5] |
|
Call math functions directly using [funct] Note that these are exported [expr] functions, not commands. They are exported via [funct] |
puts [funct log10 2] puts [funct round $pi 2] puts [funct rnd 1 10] puts [* [funct atan 1] 4] |
|
Loop until the user presses the ESC key [inkey] checks if a keystroke is available, if so, the key code is returned |
puts "Press ESC to halt" while {1} { puts "Looping" sleep 1000 if {== [inkey] 27} { # ESC is 27 stop } } |
|
Define a procedure which uses recursion and call it | proc factorial {val} { puts "Current level: [info level] - val: $val" set lvl [info level] if {$lvl == $val} { return $val } return [expr {($val-$lvl) * [factorial $val]}] } puts [factorial 3] |
|
The Ticol [loop] command is simpler and more efficient than [for] or [while] | option expression off loop i 0 10 1 { if {== $i 5} { # Skip 5 and increment past 6 ++ i continue } } puts "i is $i" |
|
Show RPN representation of an expression in the
CLI using [calc] instead of [expr] Will display: [- [+ [+ [* 4 4] [* 4 4]] 4] [* 4 4]] |
option preprocessor off puts [explain 4*4+4*4+4-4*4] |
|
Define an 'at exit' command The defined
procedure will print "* Bye *" when a script exits |
if { ! [defined at_exit] } { proc at_exit {} { newline textcolor yellow puts "* Bye *" textcolor } } |
|
Run obfuscated Tcl code within a Tcl script
using the TCX plugin (hello world script) ticol.exe filename.tcl /c /64 is used to generate the code which will be saved as a *.t64 file. These files may also be read in using [readfile] and then executed using the TCX module |
# Uses default encryption # Encode using ticol.exe filename.tcl /c and base64.exe filename.tcx set code "ibuhXiuUJYyD6Uubn7G4L/wRkK/BtOkaHi5IFuULSaPU/3YskM139 seIuesC7KEhQjprZY83PyZAqzgwFmlfnIkpMMmSMvs= # Load the plugin lib ticol_tcx # Execute the code (call [eval] internally) tcx run $code # Unload the plugin lib ticol_tcx -unload |
|
Unwrap a multi-level list Will return: catlog cd title Empire Burlesque artist Bob Dylan Company Columbia Price 10.90 year 1985 |
set l "{catlog {cd {{title {Empire Burlesque}} {artist {Bob Dylan}} \ {Company Columbia} {Price 10.90} {year 1985} }}}" proc unwrap_as_text {l} { global foo::s # Avoid clashes with root a namespace var foreach x $l { if {> [ldepth $l] 2} { unwrap_as_text $x } else { append ::foo::s "$x\t" } } if {<= [ldepth $l] 2} { set ::foo::s [string trim $::foo::s "\t "] append ::foo::s "\r\n" } } unwrap_as_text $l textcolor cyan puts $foo::s textcolor |
|
You can use the dreaded goto if
confined within a special [goto_block] There is nothing wrong with using a goto in the right place |
option expression off set s 0 goto_block { start { # Loop start ++ s # Increment if {> $s 9} { # Test goto end # Exit } goto start # Loop end } end { textcolor magenta puts "Puts exited OK at label: end" textcolor } } |
|
Read in a web page and split off the HTML tags Prints out.. e.g. <html> <head> <title> </title> </head> <body bgcolor="#000000" text="#C0C0C0" link="#00FFFF"> ... |
option expression off lib ticol_html set s [http http://localhost:8800] do { set t [html chomp_tag s] puts $t } while {ne $t ""} |
|
Read the registry to get a file association For
me, this returns: |
lib ticol_reg set ext .tcl # Read the type name set type [registry get HKEY_CLASSES_ROOT\\$ext {}] # Work out where to look for the command set path HKEY_CLASSES_ROOT\\$type\\Shell\\Open\\command # Read the command set command [registry get $path {}] puts "Filetype \"$ext\" opens with $command" |
|
Use the [md5] command to generate random numbers | set seed $pi # Could instead use [clock] set counter 0 # Global/static counter option expression on # Use [expr] for flow-control proc hashrnd {lo hi} { # Hash-generated random number upvar seed # Not recursive. No level given upvar counter incr counter set r [md5 [+ $counter $seed]] # Get an MD5 value set q "0x" # Literal 0x prefix append q [mids r [% $counter 24] 8] # Slice a section of the MD5 return [expr "$q % (($hi-$lo)+1)+$lo"] # Evaluate as hexadecimal } option expression on for {set i 0} { $i < 10000} {incr i} { puts "hashrnd 10..10000:\t[hashrnd 10 10000]" } |
|
Colourised fractal image generator, ported from
very old MBASIC source. Can optionally print out in original numeric text format
The orange colouration is a custom colour set for darkyellow which can be set by Ticol (see console_orange.bat) We use [calc] for maximum performance. This is a macro-stage optimising form of [expr] which will expand expresisions into inline Tcl prior to runtime and simplify arguments where possible e.g. set t [calc ($a*$a)-($b*$b)+$ca] becomes |
option expression off const print_numbers $false # Set to $true to print numbers in BASIC style for {set y -12} {<= $y 12} {++ y} { set print_space $true puts " " -nonewline for {set x -39} {<= $x 39} {++ x} { set ca [* $x 0.0458] set cb [* $y 0.08333] set a $ca set b $cb for {set i 0} {<= $i 15} {++ i} { set t [calc ($a*$a)-($b*$b)+$ca] set b [calc (2*$a*$b)+$cb] set a $t if {> [calc ($a*$a)+($b*$b)] 4} { if {> $i 9} { += i 7 } textcolor [+ [% $i 7] 1] if {$print_numbers} { puts [chr [+ 48 $i]] -nonewline } else { puts [chr 0xdb] -nonewline } textcolor set print_space $false break } } if {$print_space} {puts " " -nonewline} set print_space $true } newline } |
|
A Brainfuck interpreter
in Ticol Tcl based on Petter Wahlman's C++ source and which is capable of running
simple/short BF scripts Brainfuck is inefficient and performs a huge number of iterations on anything much larger than "Hello world" This script defines a proc [array_to_string_asc] to convert the byte-array back to ASCII text Makes use of enhanced [store] command as well as single-character [index] Note that "[" and "]" must be stored in 'escaped' form within the Tcl string and the routine called using [unescape]. [switch] will compare using the -exact (default) option Note the byte-width cropping for [++] and [--] when incrementing the tape contents. This is to keep the value within 0..255 ASCII range Text-input for "," requires improvement For full performance, Brainfuck would be need to be implemented as a plugin DLL The same code, compiled in C++ as bf.exe is available for speed-comparison |
option expression off proc array_to_string_asc {arr} { ################################################# # Convert an integer byte-array to ASCII string ################################################# upvar $arr a for {set i 0} {< $i [array size a]} {++ i} { store add [chr $a($i)] } return [store get] } const MAX_NESTING 100 const MAX_TAPE 6000 # Size of the processing arena const MAX_LOOPS 100000 # Infinite loop protection proc bf {prog} { set prog_len [string length $prog] if {$prog_len} { set output_len 0 dim tape $::MAX_TAPE for {set i 0} {< $i $::MAX_TAPE} {++ i} { set tape($i) 0 } set edi 0 set eip 0 set loop_protection $::MAX_LOOPS set depth 0 set state 0 dim loopstart 100 set loopstart(0) 0 set result(0) "" set count 0 while {&& [< $eip $prog_len] [-- loop_protection]} { if {strchr [unescape "<>+-.,[]"] [index $prog $eip]} { ++ count switch [index $prog $eip] { "<" { # < [chr 60] - decrement the data pointer if {$edi} { -- edi } } ">" # > [chr 62] - Increment the data pointer if {< $edi $::MAX_TAPE} { ++ edi } } "+" { # + [chr 43] ++ tape($edi) -width 8 } "-" { # - [chr 45] -- tape($edi) -width 8 } "." { # "." [chr 46] - output the byte at the data pointer set result($output_len) $tape($edi) ++ output_len } "," { # "," [chr 44] - Accept one byte of input set tape($edi) [getkey] } "[" { # "[" [chr 91] set state 1 if {== $tape($edi) 0} { while {&& [!= $state 0] [< [++ eip] $prog_len]} { if {eq [index $prog $eip] "]"} { -- state } elseif {eq [index $prog $eip] "["} { ++ state } } } elseif {< $depth $::MAX_NESTING} { set loopstart([++ depth]) $eip } } "]" { # "]" [chr 93] if {!= $depth 0} { if {== $tape($edi) 0} { -- depth } else { set eip $loopstart($depth) } } } } } ++ eip } } if {<= $loop_protection 0} { puts "Error: Infinite loop detected" die } return [array_to_string_asc result] } set s " +++++ +++++ Iinitialise counter (c0) to 10 \[ > +++++ ++ Add 7 to cell #1 > +++++ +++++ Add 10 to cell #2 > +++ Add 3 to cell #3 > + Add 1 to cell #4 <<<< - Decrement counter (cell #0) \] > ++ . Print 'H' > + . Print 'e' +++++ ++ . Print 'l' . Print 'l' +++ . Print 'o' > ++ . Print ' ' << +++++ +++++ +++++ . Print 'W' > . Print 'o' +++ Print 'r' ----- - . Print 'l' ----- --- . Print 'd' > + . Print '!' > . Print ' ' " puts [bf [unescape $s]] |
|
Maze generator ported from classic BASIC | option expression off proc maze {width height {char "²"}} { dim maze [* $width $height] loop x 0 $width 1 { loop y 0 $height 1 { set maze($x,$y) " " } } set currentx [int [* [funct rand] [- $width 1]]] set currenty [int [* [funct rand] [- $height 1]]] if {= [% $currentx 2] 0} {++ currentx} if {= [% $currenty 2] 0} {++ currenty} set maze($currentx,$currenty) $char set done 0 set loops [* [* $width $height] 20] while {! $done} { loop i 0 $loops { set oldx $currentx set oldy $currenty set j [rnd 0 3] if {== $j 0} { if {< [+ $currentx 2] $width} {++ currentx 2} } elseif {== $j 1} { if {< [+ $currenty 2] $height} {++ currenty 2} } elseif {== $j 2} { if {> [- $currentx 2] 0} {-- currentx 2} } elseif {== $j 3} { if {> [- $currenty 2] 0} {-- currenty 2} } if {eq $maze($currentx,$currenty) " "} { set maze($currentx,$currenty) $char set maze([/ [+ $currentx $oldx] 2],[/ [+ $currenty $oldy] 2]) $char } } set done 1 } loop y 0 $height { loop x 0 $width { puts $maze($x,$y) -nonewline } newline } } maze 40 30 * |
|
Windows service control query using
ticol_calldll plugin This shows struct, const and enum declaration as well as the use of a base struct as a typdef using [new]
Returns
|
option escape on lib ticol_calldll -nocomplain newline struct type_service_status { # Can be used as a typedef with [new] {dwServiceType 4} {dwCurrentState 4} {dwControlsAccepted 4} {dwWin32ExitCode 4} {dwServiceSpecificExitCode 4} {dwCheckPoint 4} {dwWaitHint 4} } enum { SERVICE_STATUS_UNKNOWN # 0 SERVICE_STOPPED # 1 SERVICE_START_PENDING # 2 SERVICE_STOP_PENDING # 3 SERVICE_RUNNING # 4 SERVICE_CONTINUE_PENDING # 5 SERVICE_PAUSE_PENDING # 6 SERVICE_PAUSED # 7 } const SC_MANAGER_ENUMERATE_SERVICE 0x0004 struct service_status { {dwServiceType 4} {dwCurrentState 4} {dwControlsAccepted 4} {dwWin32ExitCode 4} {dwServiceSpecificExitCode 4} {dwCheckPoint 4} {dwWaitHint 4} } if {! [defined is_service_running]} { proc is_service_running {service_name} { ############################################################# # Query a service by name ############################################################# # [in] Registered service name # Note: Not the descriptive name # [return] Boolean status value ############################################################# option expression push on upvar true upvar false set r $false new ::type_service_status ss set scm_handle [calldll Advapi32 OpenSCManagerA [info hostname] 0 $::SC_MANAGER_ENUMERATE_SERVICE] if {$scm_handle > 0} { set sc_handle [calldll Advapi32 OpenServiceA $scm_handle $service_name $::SC_MANAGER_ENUMERATE_SERVICE] if {$sc_handle > 0} { set r [calldll Advapi32 QueryServiceStatus $sc_handle ss] calldll Advapi32 CloseServiceHandle $sc_handle if {[ofaddressb ss.dwCurrentState] == $::SERVICE_RUNNING} { set r $true } } calldll Advapi32 CloseServiceHandle $scm_handle } option expression pop return $r } } textcolor white darkmagenta puts " * "Distributed Link Tracking Client" Running? [bool [is_service_running {trkwks}]]" puts " * "No Such Service FooBar" Running? [bool [is_service_running {FooBar}]]" puts " * "Themes" Running? [bool [is_service_running {Themes}]]" -nonewline textcolor newline |
|
A CGI POST Example which is driven from the
following HTML code <!- Web form --> |
option expression off puts "Content-type: text/html\r\n\r\n" # Be sure to send this first lib ticol_cgi -nocomplain set s [cgi] # Call to set $_POST if {> $s 0} { # Test for -ve error return puts "<font color="blue"><strong>" array foreach _POST val subscript { puts "<br>Result $_POST($subscript)='$val'<br>" } puts "</strong></font>" } |
|
Display an ANSI Art File | lib ticol_ansi set s [readfile art.ans] ansi $s -fix lib ticol_ansi -unload |
|
Towers of Hanoi in Tcl A more extensive example. Being highly-iterative, this is vastly slower than the identical, compiled C++ hanoi.exe example available here for comparison This runs in about 4 seconds for 17 rings on an old-generation i5 with several other items multi-tasking The compiled C+ exe version of the same code takes 68ms for 17 rings. Any iterative intensive code which runs outside of a Ticol native command will run a couple of orders of magnitude slower than compiled code. Since DLL plugins are quite easy to produce with the quickest time to create a plugin being about 20 minutes, I would move any frequently used or computationally-intensive code into a separate plugin command |
option expression off cls set n 0 set yloc 1 if {! [defined draw_status]} { proc draw_status {} { upvar yloc gotoxy 1 $yloc upvar a upvar b upvar c upvar n upvar moves upvar x textcolor white puts "Towers of Hanoi ($n) - Pole Status ([comma $x] moves)\n" textcolor red printf "Pole A(%2i) %-67s\r\n" [stack count a] [stack list a] textcolor yellow printf "Pole B(%2i) %-67s\r\n" [stack count b] [stack list b] textcolor green printf "Pole C(%2i) %-67s\r\n" [stack count c] [stack list c] textcolor } } textcolor white blue puts " Towers of Hanoi " textcolor newline if {[< $argc 2]} { puts "How many rings (2..25)? " -nonewline gets stdin n if {|| [== $n ""] [== $n 0]} {stop} } else { set n $argv(1) } set n [min $n 25] stack create a $n # Init 3 stacks to given size stack create b $n stack create c $n for {set x 0} {[< $x $n ]} {++ x} { # Create initial stack of hoops stack push a [+ $x 1] # 0..n-1 } set x 1 set shln [<< 1 $n] # Precalc (1 << $n), << for n draw_status set start_secs [clock seconds] set start [timer] while {< $x $shln} { set xminus1 [- $x 1] stack push [chr [calc ((($x|$xminus1) + 1) % 3 )] 97] \ [stack pop [chr [calc ($x & $xminus1) % 3] 97]] is_mod $x 50000 draw_status ++ x } set end [timer] set end_secs [clock seconds] draw_status newline puts "Took [- $end $start] millisecond(s) and [comma $x] move(s)" puts "Took [- $end_secs $start_secs] second(s) and [comma $x] move(s)" newline puts "Done." stack unset a stack unset b stack unset c |
|
A TicASM (pseudo-assembler) script to print out
4 lines Loop: 1 Loop: 2 Loop: 3 Loop: 4 |
lib ticol_asm # Load the interpreter plugin asm { org 100h ; Example only, not required ; as we won't JMP < 0x100 msg: ; Declare a variable/label buf: ; Declare a variable/label db ? "" ; Ensure AX is not printable on return mov msg, edp ; msg points to this string (EDP) db ? "Loop: " ; Define a string (6 bytes+null) mov buf, edp ; buf points to EDP db 21, 0 ; 21 byte buffer to store number mov ax, 4 ; Initialise AX mov bx, 0 ; Initialise BX to counter L1: ; Declare a label push ax ; Save AX mov ax, msg ; Point AX to our message call 9 ; Print string inc bx ; Increment BX mov [buf], bx ; Store BX value in our buffer mov ax, buf ; Point AX to our printable buffer call 9 ; Call print address at AX mov ax, 10 ; CR (newline) call 2 ; Call print char pop ax ; Restore AX dec ax ; Decrement AX jnz L1 ; If not zero then loop L1 xor ax, ax ; Clear AX end ; Finish } lib ticol_asm -unload # Unload the interpreter plugin |
Last updated on 04 March 2021 - This page is designed for 1024 x760 and higher resolution displays