Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Scripting in Tcl?

78 views
Skip to first unread message

Wojciech Kocjan

unread,
Jul 15, 2005, 8:09:48 AM7/15/05
to
Hello,

I just got a pretty weird idea, but one that could be interesting from
an academic point of view.

Is there any scripting engine in pure Tcl, one that would allow me to
convert something like this (metalanguage now, not Tcl):

for i 0 .. 9 {
putvar i
}

into
1: set i 0
2: putvar i
3: if i <= 9 goto 1

So that I can evaluate one command at a time, ie allow doing only 20
commands at a time.

I'm considering writing a small strategic game that will allow scripting
the objects, so that the players may write code for buildings/crafts and
the game will be held on the game server.

I don't want to make anything commercial out of it, just for learning.
Is there any such scripting engine, or should I consider writing one -
it seems like a good exercise anyway so maybe I would do it, but base on
something that's already done :-)

--
WK

Yuan MEI

unread,
Jul 15, 2005, 8:20:53 AM7/15/05
to
Wojciech Kocjan <moje...@kocjan.org> writes:

There are a lot of interpreters available over internet,
just google. But I never heard of anybody write one in tcl.

--
Yuan MEI

Gerald W. Lester

unread,
Jul 15, 2005, 9:31:01 AM7/15/05
to
Wojciech Kocjan wrote:
> Hello,
>
> I just got a pretty weird idea, but one that could be interesting from
> an academic point of view.
>
> Is there any scripting engine in pure Tcl, one that would allow me to
> convert something like this (metalanguage now, not Tcl):

Yes, in particular there are two versions of LOGO in Tcl, see
http://wiki.tcl.tk/logo.

--
+--------------------------------+---------------------------------------+
| Gerald W. Lester | "The man who fights for his ideals is |
| Gerald...@cox.net | the man who is alive." -- Cervantes |
+--------------------------------+---------------------------------------+

David N. Welton

unread,
Jul 15, 2005, 4:37:17 PM7/15/05
to
Wojciech Kocjan wrote:
> Hello,
>
> I just got a pretty weird idea, but one that could be interesting from
> an academic point of view.
>
> Is there any scripting engine in pure Tcl, one that would allow me to
> convert something like this (metalanguage now, not Tcl):
>
> for i 0 .. 9 {
> putvar i
> }
>
> into
> 1: set i 0
> 2: putvar i
> 3: if i <= 9 goto 1
>
> So that I can evaluate one command at a time, ie allow doing only 20
> commands at a time.

You could rewrite the commands you want to expose and have each one of
them tick off a command counter each time it is run, and if the limit
has been exceeded, yield control to the main program. Slow, but I don't
think you're looking for speed, but control...

--
David N. Welton
- http://www.dedasys.com/davidw/

Apache, Linux, Tcl Consulting
- http://www.dedasys.com/

Bob Techentin

unread,
Jul 18, 2005, 9:35:02 AM7/18/05
to
"Wojciech Kocjan" <moje...@kocjan.org> wrote

>
> Is there any scripting engine in pure Tcl, one that would allow me
to
> convert something like this (metalanguage now, not Tcl):
>
> for i 0 .. 9 {
> putvar i
> }


I don't quite understand why you would want to do this. Tcl *is* a
scripting language. Why not just expose Tcl (or a safe Tcl
interpreter) to the user? You get the whole language. It doesn't
cost much. And it works!

Bob
--
Bob Techentin techenti...@NOSPAMmayo.edu
Mayo Foundation (507) 538-5495
200 First St. SW FAX (507) 284-9171
Rochester MN, 55901 USA http://www.mayo.edu/sppdg/

David N. Welton

unread,
Jul 18, 2005, 3:24:05 PM7/18/05
to
Bob Techentin wrote:
> "Wojciech Kocjan" <moje...@kocjan.org> wrote
>
>>Is there any scripting engine in pure Tcl, one that would allow me
>
> to
>
>>convert something like this (metalanguage now, not Tcl):
>>
>>for i 0 .. 9 {
>> putvar i
>>}
>
>
>
> I don't quite understand why you would want to do this. Tcl *is* a
> scripting language. Why not just expose Tcl (or a safe Tcl
> interpreter) to the user? You get the whole language. It doesn't
> cost much. And it works!

Wojciech can correct me if I'm wrong, but I get the impression that he
only wants to let people "spend" a certain number of operations at a
time, so he needs a way to keep track of that and stop execution of the
script.

Bob Techentin

unread,
Jul 18, 2005, 3:37:04 PM7/18/05
to
"David N. Welton" <dav...@dedasys.com> wrote

>
> Wojciech can correct me if I'm wrong, but I get the impression that
> he only wants to let people "spend" a certain number of operations
> at a time, so he needs a way to keep track of that and stop
> execution of the script.


How about

# Define operations that only work N times
proc putvar {v} {
incr ::opCount
if { $::opCount > $::opMax } {return}
# do something here
}

# Create safe interp to execute user code
set slave [interp create -safe]
$slave alias putvar putvar

# Run user script
set ::opMax 10
set ::opCount 0
$slave eval $userScript

Melissa Schrumpf

unread,
Jul 18, 2005, 9:39:27 PM7/18/05
to
David N. Welton wrote:
> Bob Techentin wrote:
> > "Wojciech Kocjan" wrote

> >>Is there any scripting engine in pure Tcl, one that would allow me
> >> to convert something like this (metalanguage now, not Tcl):

> >>for i 0 .. 9 {
> >> putvar i

> > I don't quite understand why you would want to do this. Tcl *is* a
> > scripting language. Why not just expose Tcl (or a safe Tcl
> > interpreter) to the user? You get the whole language. It doesn't
> > cost much. And it works!

> Wojciech can correct me if I'm wrong, but I get the impression that he
> only wants to let people "spend" a certain number of operations at a
> time, so he needs a way to keep track of that and stop execution of the
> script.

That's my impression, too. It's an interesting problem. At first, I'd
thought he only wanted to keep track of the number of instructions.
Now, considering it in the context of a game, there may be reason to
_preclude_ execution after a certain point. Still, I don't see any
reason to explicitly obscure the Tcl underpinnings, merely make
available a safe interpreter with a limited command set.

For example:

interp create ex0 -safe

interp eval ex0 {
foreach cmd [list SOME DENIED COMMANDS] {
rename $cmd ""
}
}

interp alias ex0 foreach {} rsc_foreach

set ::MAXALLOWABLE 7

proc rsc_foreach {args} {
# example without sufficient error-handling
set cmd [lindex $args end]
set ::opcount 0
foreach {varList lst} [lrange $args 0 end-1] {
foreach $varList $lst {
incr ::opcount [llength $varList]
eval $cmd
if {$::opcount>$::MAXALLOWABLE} {break;}
}
}
}

% interp eval ex0 {
foreach {a b c} {1 2 3 4 5 6 7 8 9 10 11 12 13 14} {puts "$a $b
$::opcount"}
}
1 2 3
4 5 6
7 8 9

In this way, you can control execution and terminate after some maximum
allowable number of "executions" have taken place. Such "break"
statements will have to be liberally applied, depending on how one
defines an "instruction" and how "strict" one intends to be with
"virtual processor time," whether "partially-complete" instructions are
allowed to complete, etc.

Obviously, it's up to the OP to decide what constitutes an
"instruction," how "strict" to be with interrupting incomplete
instructions, and whether to penalize or simply restrict being
"resource-overdrawn."

Also, when one implements the "accessible" function set in the safe
interp, one will have to define the "cost" of each function. I used
[foreach] as an example, because it can be "costed" several ways: once
per iteration, "n" times per iteration, depending on the number of
variables assigned (shown above), etc.

It should be noted that the example code provided is NOT safe, and does
NOT prevent the unruly person from setting ::MAXALLOWABLE. Perhaps
"set" should also be redefined, or a [trace] put on it, to prevent
critical variables from being modified. Or perhaps more indirection
should be used in the [interp alias]ing.

Interesting problem. I should like to see what becomes of it.

--
MKS

Wojciech Kocjan

unread,
Jul 19, 2005, 2:42:22 AM7/19/05
to
Melissa Schrumpf napisał(a):

>>Wojciech can correct me if I'm wrong, but I get the impression that he
>>only wants to let people "spend" a certain number of operations at a
>>time, so he needs a way to keep track of that and stop execution of the
>>script.
> That's my impression, too. It's an interesting problem. At first, I'd
> thought he only wanted to keep track of the number of instructions.
> Now, considering it in the context of a game, there may be reason to
> _preclude_ execution after a certain point. Still, I don't see any
> reason to explicitly obscure the Tcl underpinnings, merely make
> available a safe interpreter with a limited command set.

Yes correct. Well, the exact idea is/was to allow any given code to be
stopped and resumed.

For example:

while {true} {
while {[get_damage] < 60} {
go_to_weaponrange [search_nearest_enemy]
attack [search_nearest_enemy]
}
go_to_base
repair
}

Now the idea is that once I call go_to_weaponrange, the script would
stop execution until the object actually gets there (which chould take a
lot of time). If I just replace the while and throw a break, then I will
get out of that script, but I will have no idea how to get back to it.

I thought of vwait, but I remember those are waiting in a particular order.

> Interesting problem. I should like to see what becomes of it.

Well I didn't think of any solution so I thought of moving to event
based scripting of the objects. For the example above that would be:

proc x1 {} {
if {[get_damage] >= 60} {x3; return}
go_to_weaponrange [search_nearest_enemy]
on idle x2
}

proc x2 {} {
if {[get_damage] >= 60} {x3; return}
attack [search_nearest_enemy]
on idle x1
}

proc x3 {} {
go_to_base
on idle {repair; on idle x1}
}

As you can see the script here is much less readable, but not sure if
this will be true for more complex scenarios.

--
WK

Donal K. Fellows

unread,
Jul 19, 2005, 6:25:40 AM7/19/05
to
Wojciech Kocjan wrote:
> Yes correct. Well, the exact idea is/was to allow any given code to be
> stopped and resumed.

That's really quite tricky to get right.

> For example:
>
> while {true} {
> while {[get_damage] < 60} {
> go_to_weaponrange [search_nearest_enemy]
> attack [search_nearest_enemy]
> }
> go_to_base
> repair
> }
>
> Now the idea is that once I call go_to_weaponrange, the script would
> stop execution until the object actually gets there (which chould take a
> lot of time). If I just replace the while and throw a break, then I will
> get out of that script, but I will have no idea how to get back to it.
>
> I thought of vwait, but I remember those are waiting in a particular order.

The easiest way to solve this is probably to use threads and 8.5's
resource-limited interpreters. The threads allow you to suspend
execution at arbitrary spots without significant difficulty, and the
resource-limited interpreter system means you eventually regain control
even if the code goes beserk (ignoring memory leaks for now).

Alternatively, you could try using some of the things I wrote on
http://wiki.tcl.tk/897 (search within the page for coroutines). But
you'd need to restructure the code a bit to do that; that code isn't
designed to work with anything even approaching preemption...

Donal.

Wojciech Kocjan

unread,
Jul 19, 2005, 6:31:49 AM7/19/05
to
Donal K. Fellows napisał(a):

> The easiest way to solve this is probably to use threads and 8.5's
> resource-limited interpreters. The threads allow you to suspend
> execution at arbitrary spots without significant difficulty, and the
> resource-limited interpreter system means you eventually regain control
> even if the code goes beserk (ignoring memory leaks for now).

Well, you should do some tests on running that with 10000 objects (even
if I don't succeed, I consider this a good skill to learn, so I assume
huge values for now). I can't even use [interp create -safe] for each
object, not to mention threads...

> Alternatively, you could try using some of the things I wrote on
> http://wiki.tcl.tk/897 (search within the page for coroutines). But
> you'd need to restructure the code a bit to do that; that code isn't
> designed to work with anything even approaching preemption...

Now this is a very good solution. And I think a carefully coded state
machine would be very useful for other things as well - like network
daemons. I actually didn't want to do any hardcore code splitting, but I
would like the scripting engine to be easily written.

Thanks a lot.

--
WK

Helmut Giese

unread,
Jul 19, 2005, 7:31:18 AM7/19/05
to
On Tue, 19 Jul 2005 11:25:40 +0100, "Donal K. Fellows"
<donal.k...@manchester.ac.uk> wrote:

>
>The easiest way to solve this is probably to use threads and 8.5's
>resource-limited interpreters. The threads allow you to suspend
>execution at arbitrary spots without significant difficulty

Hi Donal,
did I miss something? Last time I looked, I didn't see any possibility
to suspend a Tcl thread.
Thanks and best regards
Helmut Giese

Donal K. Fellows

unread,
Jul 19, 2005, 8:08:49 AM7/19/05
to
Helmut Giese wrote:
> did I miss something? Last time I looked, I didn't see any possibility
> to suspend a Tcl thread.

What do you think [thread::wait] does? It just happens to wait for
messages while suspended.

Donal.

Bob Techentin

unread,
Jul 19, 2005, 9:06:04 AM7/19/05
to
"Donal K. Fellows" <donal.k...@manchester.ac.uk> wrote
>
> Alternatively, you could try using some of the things I wrote on
> http://wiki.tcl.tk/897 (search within the page for coroutines). But
> you'd need to restructure the code a bit to do that; that code isn't
> designed to work with anything even approaching preemption...

Wow! That's really cool, Donal. Strange, but cool. It must be the
teenager in me that appreciates a paper that presents a technique, explains
why all the coding standards are wrong, and still uses the terms
"monstrosity" and "hackery." :-)

But even if co-routines are ugly in C, shouldn't it be possible to implement
them in some rational and elegant way in Tcl? I'm imagining a "coroutine"
command, similar to proc, which accepts a number of name-args-body routine
definitions. When a coroutine is called, a state machine runs the routines,
one command at a time, and interpreting crReturn and crFinish. Or would
there be a better way to implement it?

Helmut Giese

unread,
Jul 19, 2005, 9:31:32 AM7/19/05
to

Ok, I misinterpreted your remark to which I reacted. By 'suspend' I
mean the possibility for one (master) thread to actively suspend
another thread, which will then do nothing until 'resumed' by the
master.
thread::wait serves a quite different purpose.

Donal K. Fellows

unread,
Jul 19, 2005, 10:17:34 AM7/19/05
to
Bob Techentin wrote:
> But even if co-routines are ugly in C, shouldn't it be possible to implement
> them in some rational and elegant way in Tcl? I'm imagining a "coroutine"
> command, similar to proc, which accepts a number of name-args-body routine
> definitions. When a coroutine is called, a state machine runs the routines,
> one command at a time, and interpreting crReturn and crFinish. Or would
> there be a better way to implement it?

I'm not sure. The difficult bit (if you're going to do the job properly)
is that you need the ability to resume at an arbitrary point through the
coroutine. The Tcl interpreter's just not designed to operate that way
at the moment.

Donal.

Bruce Hartweg

unread,
Jul 19, 2005, 10:28:45 AM7/19/05
to

have a look at tclrobots <http://www.nyx.net/~tpoindex/tcl.html#TclRobots>

it's based on an old version of tcl, but it does what you are looking for
(it's also a really cool game!)

Bruce

Neil Madden

unread,
Jul 20, 2005, 3:54:42 PM7/20/05
to

Sounds like you want a continuation (http://wiki.tcl.tk/continuation),
or a coroutine. The authors of Eve online game used Stackless Python for
exactly this reason: http://www.eve-online.com . Scheme supports
continuations directly, as do a few other languages. Python, Lua, and
Perl (I believe) all have some support for coroutines, which might be
able to do what you want. Tcl, unfortunately, has no built-in support
for this kind of thing, and it is difficult to do without writing your
own state-machine language. Jim may be able to handle it
(http://jim.berlios.de/), if you want to stick with something Tcl-ish,
but I can't remember how far Salvatore went down that road.

Alternatively, I think the rule/event-based approach works extremely
well for this sort of thing. See below.

>
> I thought of vwait, but I remember those are waiting in a particular order.
>
>> Interesting problem. I should like to see what becomes of it.
>
>
> Well I didn't think of any solution so I thought of moving to event
> based scripting of the objects. For the example above that would be:
>
> proc x1 {} {
> if {[get_damage] >= 60} {x3; return}
> go_to_weaponrange [search_nearest_enemy]
> on idle x2
> }
>
> proc x2 {} {
> if {[get_damage] >= 60} {x3; return}
> attack [search_nearest_enemy]
> on idle x1
> }
>
> proc x3 {} {
> go_to_base
> on idle {repair; on idle x1}
> }
>
> As you can see the script here is much less readable, but not sure if
> this will be true for more complex scenarios.

It depends. If your scripts have a fairly straight-forward linear flow,
and are mostly goal-driven, then the procedural approach can work very
well. However, if your code is more event-driven (as is often the case
in computer games), or if the logic is likely to involve many levels of
branching conditions, then I think a rule-based approach really starts
to shine. You could use Prolog or CLIPS for this kind of work, but Tcl
can handle this kind of thing perfectly well on its own, provided you
know a little about agent architectures:

proc goal {name} {
global GOAL
set GOAL $name
}
proc plan {goal body} {
global GOAL ACTIONS ELSE
set GOAL $goal
set ACTIONS($GOAL) [list]
set ELSE($GOAL) ""
uplevel #0 $args
}
proc when {cond _do_ action} {
global ACTIONS GOAL
lappend ACTIONS($GOAL) $cond $action
}
proc otherwise {action} {
global ELSE GOAL
set ELSE($GOAL) $action
}
# Typically, "run" would be incorporated into your game main-loop
proc run {} {
global ACTIONS GOAL ELSE
while 1 {
set fired 0
foreach {cond action} $ACTIONS($GOAL) {
if {[uplevel #0 [list expr $cond]]} {
uplevel #0 $action
set fired 1
break; # Only first matching action fires
}
}
if {!$fired} {
uplevel #0 $ELSE($GOAL)
}
}
}

This allows you to write the code something like:

set health 100 ;# Current health
set range 100 ;# Maximum weapons range
set nearest 50 ;# Distance of nearest enemy
set target ... ;# Position of target
set base ... ;# Position of base
# etc ...

plan Attack {
when {$health < 40} do { goal Retreat }
when {$nearest < $range} do { fire_at $target }
otherwise { move_to $target }
}
plan Retreat {
when {$health > 60} do { goal Attack }
when {![at $base]} do { move_to $base }
otherwise { repair }
}
goal Attack
run

I think that looks even clearer than the original code, and certainly
becomes easier to understand as many more conditions are added. The
particular agent architecture implemented here is a variation of a
Teleo-Reactive architecture
(http://citeseer.ist.psu.edu/nilsson94teleoreactive.html), and should be
a pretty good fit for games. Essentially, each goal has a number of
rules which are listed in order of importance (a plan). At each cycle,
the rules of the current plan are scanned in order -- the first one that
matches is executed. Typically, the first rule is what determines when
this goal is satisfied, the others are subgoals (and sub-subgoals etc).
The beauty of this architecture is that it is simple, and has the
advantage that if you "accidentally" satisfy a rule higher-up the list,
then the agent will notice it instead of blindly following the whole
plan. For example, in the "Retreat" plan, if your agent somehow
magically gets a boost of health on the way back to base, then it will
stop running to the base, and turn round and attack the enemy again.

Cheers,

-- Neil

0 new messages