Updated 2008-01-29 09:57:01 by dkf

George Peter Staplin - Have you ever wanted to treat strings as objects? Well, now you can.

I'm also working on a class system like Smalltalk's, which will probably appear here in a little while.
  #! /usr/local/bin/tclsh8.3
  proc unknown {args} {

	if {[string is integer [lindex $args 0]]} {
	return [expr $args]
	}

	if {[string index [lindex $args 0] 0] == "@"} {
	set astring [string range [lindex $args 0] 1 end]
	set numArgs [llength $args]
		for {set i 1} {$i < $numArgs} {incr i} {
		set arg$i [lindex $args $i]
		}
		switch -- $arg1 {
			byteLength {
			return [string bytelength $astring]
			}

			first {
				if {$numArgs < 3} {
				error "Please use \"@string1\" first \"string2\" ?startIndex?"
				}
				if {$numArgs == 3} {
				return [string first $astring $arg2]
				}
				if {$numArgs == 4} {
				return [string first $astring $arg2 $arg3]
				}
			}

			index {
				if {$numArgs < 3} {
				error "Please use \"@string\" index num  Where num is the index."
				}
				return [string index $astring $arg2]
			}
			is {
				if {$numArgs < 3} {
				error "Please use \"@string\" is class  Where class is alpha, digit, etc."
				}
			return [string is $arg2 $astring]
			}

			toLower {
			return [string tolower $astring]
			}

			toUpper {
			return [string toupper $astring]
			}

			default {
			}
		}
	}
  }

Test code:
  puts [2 + 3 + 2]
  puts ["@HELLO WORLD" toLower]

  proc hello {args} {puts $args}
  hello world

  puts ["@hello world" toUpper]

  puts ["@blahblah" byteLength]
  puts ["@wonder" first "wonder"]
  puts ["@wonder" first "wonder" 0]
  set blah "long string"
  puts ["@$blah" index 1]
  puts ["@long string" index 5]
  puts ["@fun" is ascii]

To get a real @ at the beginning of a string double it (just like in Smalltalk). I suggest that you don't name a proc @something, because it will mess this up.

RS: One note - @ isn't exactly beautiful as string marker. The Tcl parser consumes double quotes, so you don't see those in the unknown proc. But single quotes are available - only they don't group... You could modify the validity check so the examples could be
 puts ["'hello world'" toUpper]
 puts ['blahblah' byteLength]

GPS - How about this:
  #! /usr/local/bin/tclsh8.3

  #Kevin Kenny wrote the original source and meth (formerly to) procs.
  #I've made it so that having multiple files is not needed and
  #a bunch of other stuff.

  proc cfork {} {
  global cstate

	if {[info exists cstate] != 1} {
	set cstate 1
	rename source ::tcl::source

		proc source {fileName} {
		variable source_channel
		set source_channel [open $fileName r]
		set command {}
		set sep {}
		set escaped 0
			while {[gets $source_channel line] >= 0} {

				for {set i 0} {$i < [string length $line]} {incr i} {

					set ch [string index $line $i]

					if {$ch == "'" && $escaped == 0} {
					#puts "WONDER $i"
					#puts $line
					set line [string replace $line $i $i "\""]
					} elseif {$ch == "\\"} {
					set escaped 1
					} else {
					set escaped 0
					}

				}

			append command $sep $line

				if {[info complete $command]} {
				set result [uplevel 1 $command]
				set command {}
				set sep {}
				} else {
					set sep \n
				}
			}
		return $result
		}

		proc meth {name args} {
		variable source_channel
		set body {}
		set sep {}

			while {[gets $source_channel line] >= 0} {
				if {[string equal [string trim $line] @]} {
				return [proc $name $args $body]
				} else {
					append body $sep $line
					set sep \n
					}
			}
		return -code error {unterminated 'meth'}
		}

		proc unknown {args} {
		#puts $args
		set numArgs [llength $args]
		set astring [lindex $args 0]

		for {set i 1} {$i < $numArgs} {incr i} {
		set arg$i [lindex $args $i]
		}

			if {[string is integer $astring] || [string index $astring 0] == "("} {
			return [expr $args]
			}

		set sop [lindex $args 1]

		#puts "sop $sop astring $astring"

			switch -- $sop {
				byteLength {
				return [string bytelength $astring]
				}

				first: {
					if {$numArgs < 3} {
					error "Please use \"string1\" first \"string2\" ?startIndex?"
					}

					if {$numArgs == 3} {
					return [string first $astring $arg2]
					}
					if {$numArgs == 4} {
					return [string first $astring $arg2 $arg3]
					}
				}

				index: {
					if {$numArgs < 3} {
					error "Please use \"string\" index num  Where num is the index."
					}
				return [string index $astring $arg2]
				}

				is: {
					if {$numArgs < 3} {
					error "Please use \"string\" is class  Where class is alpha, digit, etc."
					}
				return [string is $arg2 $astring]
				}

				toLower {
				return [string tolower $astring]
				}

				toUpper {
				return [string toupper $astring]
				}

				default {
				}
			}
		}
	source [info script]
	return 1
	}
  return 0
  }

	if {[cfork] == 1} {
	return
	}

  meth add x y
  return [expr {$x + $y}]
  @

  puts [add 6 27]
  puts ['This is Rebecca\'s flower not George\'s' toLower]
  puts ['Hello Bob!' toUpper]
  puts ['Blah Blah!' index: 0]
  puts ['hmm' is: ascii]
  puts [200 + 23]
  puts [(200 + 33000)]