scrobj(n) scrobj(n)
NAME
scrobj - scripted Tcl_Objects
SYNOPSIS
package require Tcl 8.5
package require scrobj 1.0
scrobj register typeName updateCode parseCode
scrobj convert typeName string ?extraArg?
scrobj value typeName intRep ?extraArg?
scrobj eval typeName code
scrobj info ?typeName?
DESCRIPTION
Tcl's fundamental mantra says that "everything is a string" and much of
Tcl's simplicity derives from this rule. Strings can, however, carry
with them an internal representation as some other data type, and this
is one reason for Tcl's astonishing performance. For example, Tcl
maintains such an internal representation for integers, lists, byte-
codes, and many other types.
Extension writers have long been able to add new custom types using
Tcl's C interface. The scrobj package allows you to do the same from
the script level.
Unfortunately, in order to achieve this the package has to violate a
basic assumption of Tcl's bytecode engine, namely that it is not get-
ting invoked recursively from a call to Tcl_GetString. A consequence
is that scrobj can be used to crash the application, although this does
require a rather contrived setup.
To prevent such crashes in the legitimate cases the package uses a ded-
icated Tcl interpreter for every registered type. Both Tcl 8.5 and Tcl
8.6 seem to tolerate recursive invocations of the bytecode engine, as
long as they execute in different interpreters.
Usage
scrobj register typeName updateCode parseCode
To register a new data type you have to write code to transform
the string value to the internal representation and vice versa.
This code then needs to be registered using scrobj register.
scrobj register mytype {
intRep {
# code to generate the string
# from the internal representation
set dummy "<$intRep>"
}
} {
stringVal {
# code to generate the internal
# representation from the string
regexp -- {^<([0-9]+)>$} $stringVal -> irp
set irp
}
}
The call to scrobj register implicitly creates a new Tcl inter-
preter where the conversion routines will be executed.
scrobj convert typeName string ?extraArg?
Return the internal representation corresponding to string for
the type typeName. If the internal representation is already
available, that value will be returned directly. Otherwise the
internal representation will first be generated by applying the
type's parseCode to the given string.
# convert "<4711>" and return the internal representation "4711"
scrobj convert mytype <4711>
scrobj value typeName intRep ?extraArg?
Generate a value from the internal representation.
# generate a value for type mytype
set a [scrobj value mytype 123]
# the next call will not invoke the
# parseCode because $a already has an internal
# representation for type "mytype"
set b [scrobj convert mytype $a]
# the next line will run the updateCode for mytype
set c "a is $a" ;# -> "a is <123>"
scrobj eval typeName code
Execute some code in the interpreter for type typeName.
scrobj info ?typeName?
Return the list of all registered types, or information about
one specific type.
Parametrized types
The convert and value subcommands allow to specify an optional extra
argument. This can be used to parametrize a data type by a value:
Example:
# register a parametrized type that uses a configurable
# regular expression for the conversion
scrobj register rexp {
irep {
# string is kept in internal rep
lindex $irep 1
}
} {
{inString regExp} {
regexp -- $regExp $inString -> res
list $res $inString
}
}
set pat1 {^foo([0-9]+)$}
set pat2 {^foo([0-9]+)$}
set pat3 {^fo(o[0-9]+)$}
set a [scrobj value rexp [scrobj convert rexp foo17 $pat1] $pat1]
# The next line will use the internal representation
# of $a that's already there
set b [scrobj convert rexp $a $pat1]
# The next line will re-generate the internal
# representation, because $pat1 and $pat2
# are not represented by the same Tcl_Object.
set c [scrobj convert rexp $a $pat2]
# The next line also re-generates the internal
# representation. This time the return value
# is actually different.
set d [scrobj convert rexp $a $pat3]
Application examples
scrobj comes equipped with two packages, scrobj::expression and
scrobj::rational, which illustrate the use of scrobj types.
scrobj::expression
scrobj::expression implements a parser for simple arithmetic
expressions.
package require scrobj::expression 1.0
scrobj convert scrobj::expression {23*foo(bar(12),3+$a/7)} {
opns ::operatorns::
funcns ::funcns::
}
# the result of the conversion is Tcl code that
# implements the computation:
{
upvar 1 a 3
set 1 [::funcns::bar [eval {set dummy 12}]]
set 2 [::funcns::foo [eval {set 1}] [eval {
set 4 [::operatorns::/ $3 7]
set 5 [::operatorns::+ 3 $4]
set 5
}]]
set 6 [::operatorns::* 23 $2]
set 6
} {23*foo(bar(12),3+$a/7)}
The parser is implemented in Tcl using regular expressions. It
doesn't have to be fast, because the script will be cached in
the internal representation once it is generated.
scrobj::rational
The scrobj::rational package implements rational number arith-
metic. A rational number p/q is represented internally as the
list [list $p $q].
package require scrobj::rational 1.0
scrobj::rational numerator 17272/9128 ;# -> 17272
scrobj::rational + 1727/162 8348/1731 ;# -> 1447271/93474
scrobj::rational uses scrobj::expression to implement its own
version of the expr command:
package require scrobj::rational 1.0
# register some custom functions
namespace eval rational {
namespace eval func {
proc f1 {a b} {
::scrobj::rational expression {3*$a-17/93*$b}
}
proc f2 {x} {
::scrobj::rational expression {2*$x-1}
}
}
}
set a 72/841
scrobj::rational expression {15*f1($a,3-$a) - f2(17/18+$a)/9}
# -> -8988239/2111751
KEYWORDS
Tcl_Object, expression, rational, scrobj
scripted Tcl_Objects 1.0 scrobj(n)
|