source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/wip/wip.tcl @ 8227d4c

1941-docker-image-to-testdevel
Last change on this file since 8227d4c was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 10.8 KB
Line 
1# ### ### ### ######### ######### #########
2##
3# (c) 2007 Andreas Kupries.
4
5# WIP = Word Interpreter (Also a Work In Progress :). Especially while
6# it is running :P
7
8# Micro interpreter for lists of words. Domain specific languages
9# based on this will have a bit of a Forth feel, with the input stream
10# segmented into words and any other structuring left to whatever
11# language. Note that we have here in essence only the core dispatch
12# loop, and no actual commands whatsoever, making this definitely only
13# a Forth feel and not an actual Forth.
14
15# The idea is derived from Colin McCormack's treeql processor,
16# modified to require less boiler plate within the command
17# implementations, at the expense of, likely, execution speed. In
18# addition the interface between processor core and commands is more
19# complex too.
20
21# ### ### ### ######### ######### #########
22## Requisites
23
24package require Tcl 8.4
25
26# For Tcl 8.{3,4} only snit1 of a suitable patchlevel is possible.
27package require snit 1.3
28
29# The run_next_* methods use set operations (x in set)
30package require struct::set
31
32# ### ### ### ######### ######### #########
33## API & Implementation
34
35snit::type wip {
36
37    # ### ### ### ######### ######### #########
38    ## API
39
40    constructor           {e}       {} ; # create processor
41
42    # Defining commands and where they dispatch to.
43    method def            {name {cp {}}} {} ; # Define a DSL command.
44    method defl           {names}        {} ; # Def many, simple names (cp = name)
45    method defd           {dict}         {} ; # s.a. name/cp dict
46    method deflva         {args}         {} ; # s.a. defl, var arg form
47    method defdva         {args}         {} ; # s.a. defd, var arg form
48
49    # Execution of word lists.
50    method runl           {alist}   {} ; # execute list of words
51    method run            {args}    {} ; # ditto, words as varargs
52    method run_next       {}        {} ; # run the next command in the input.
53    method run_next_while {accept}  {} ; # s.a., while acceptable command
54    method run_next_until {reject}  {} ; # s.a., until rejectable command
55
56    # Manipulation of the input word list.
57    method peek           {}        {} ; # peek at next word in input
58    method next           {}        {} ; # pull next word from input
59    method insert         {at args} {} ; # insert words back into the input
60    method push           {args}    {} ; # ditto, at == 0
61
62    # ### ### ### ######### ######### #########
63    ## Processor construction.
64
65    constructor {e args} {
66        if {$e eq ""} {
67            return -code error "No engine specified"
68        }
69        set engine $e
70        $self Definitions $args
71        return
72    }
73
74    method Definitions {alist} {
75        # args = series of 'def name' and 'def name cp' statements.
76        # The code to handle them is in essence a WIP too, just
77        # hardcoded, as state machine.
78
79        set state expect-def
80        set n  {}
81        set cp {}
82        foreach a $alist {
83            if {$state eq "expect-def"} {
84                if {$a ne "def"} {
85                    return -code error "Expected \"def\", got \"$a\""
86                }
87                set state get-name
88            } elseif {$state eq "get-name"} {
89                set name $a
90                set state get-cp-or-def
91            } elseif {$state eq "get-cp-or-def"} {
92                # This means that 'def' cannot be a command prefix for
93                # DSL command.
94                if {$a eq "def"} {
95                    # Short definition, name only, completed.
96                    $self def $name
97                    # We already have the first word of the next
98                    # definition here, name is coming up next.
99                    set state get-name
100                } else {
101                    # Long definition, name + cp, completed.
102                    $self def $name $a
103                    # Must be followed by the next definition.
104                    set state expect-def
105                }
106            }
107        }
108        if {$state eq "get-cp-or-def"} {
109            # Had a short definition last, now complete.
110            $self def $name
111        } elseif {$state eq "get-name"} {
112            # Incomplete definition at the end, bogus
113            return -code error "Incomplete definition at end, name missing."
114        }
115        return
116    }
117
118    # ### ### ### ######### ######### #########
119    ## Processor state
120    ## Handle of the object incoming commands are dispatched to.
121    ## The currently active DSL code, i.e. word list.
122
123    variable engine  {}      ; # command
124    variable program {}      ; # list (string)
125    variable arity -array {} ; # array (command name -> command arity)
126    variable cmd   -array {} ; # array (command name -> method cmd prefix)
127
128    # ### ### ### ######### ######### #########
129    ## API: DSL definition
130
131    ## DSL words map to method-prefixes, i.e. method names + fixed
132    ## arguments. We store them with the engine already added in front
133    ## to make them regular command prefixes. No 'mymethod' however,
134    ## that works only in engine code itself, not form the outside.
135
136    method def {name {mp {}}} {
137        if {$mp eq {}} {
138            # Derive method-prefix from DSL word.
139            set mp [list $name]
140            set m  $name
141            set n 0
142
143        } else {
144            # No need to check for an empty method-prefix. That cannot
145            # happen, as it is diverted, see above.
146
147            set m [lindex $mp 0]
148            set n [expr {[llength $mp]-1}]
149        }
150
151        # Get method arguments, check for problems.
152        set a [$engine info args $m]
153        if {[lindex $a end] eq "args"} {
154            return -code error "Unable to handle Tcl varargs"
155        }
156
157        # The arity of the command is number of required arguments,
158        # with compensation for those already covered by the
159        # method-prefix.
160
161        set cmd($name)   [linsert $mp 0 $engine]
162        set arity($name) [expr {[llength $a] - $n}]
163        return
164    }
165
166    method deflva {args}  { $self defl $args ; return }
167    method defdva {args}  { $self defd $args ; return }
168    method defl   {names} { foreach n $names { $self def $n } ; return }
169    method defd   {dict}  {
170        if {[llength $dict]%2==1} {
171            return -code error "Expected a dictionary, got \"$dict\""
172        }
173        foreach {name mp} $dict {
174            $self def $name $mp
175        }
176        return
177    }
178
179    # ### ### ### ######### ######### #########
180    ## API: DSL execution
181    #
182    ## Consider moving the core implementation into procs, to reduce
183    ## call overhead
184
185    method run {args} {
186        return [$self runl $args]
187    }
188
189    method runl {alist} {
190        # Note: We are saving the current program and restore it
191        # afterwards, this handles the possibility that this is a
192        # recursive call into the dispatcher.
193        set saved $program
194        set program $alist
195        set r {}
196        while {[llength $program]} {
197            set r [$self run_next]
198        }
199        set program $saved
200        return $r
201    }
202
203    method run_next_while {accept} {
204        set r {}
205        while {[struct::set contains $accept [$self peek]]} {
206            set r [$self run_next]
207        }
208        return $r
209    }
210
211    method run_next_until {reject} {
212        set r {}
213        while {![struct::set contains $reject [$self peek]]} {
214            set r [$self run_next]
215        }
216        return $r
217    }
218
219    method run_next {} {
220        # The first word in the list is the current command. Determine
221        # the number of its fixed arguments. This also checks command
222        # validity in general.
223
224        set c [lindex $program 0]
225        if {![info exists arity($c)]} {
226            return -code error \
227                "Unknown command \"$c\""
228        }
229
230        set n $arity($c)
231        set m $cmd($c)
232
233        # Take the fixed arguments from the input as well.
234
235        set cargs [lrange $program 1 $n]
236        incr n
237
238        # Remove the command to dispatch, and its fixed arguments from
239        # the program. This is done before the dispatch so that the
240        # command has access to the true current state of the input.
241
242        set program [lrange $program $n end]
243
244        # Now run the command with its arguments. Commands needing
245        # more than the declared fixed number of arguments are
246        # responsible for reading them from input via the method
247        # 'next' provided by the processor core.
248
249        # Note: m already has the engine at the front, it was stored
250        # that way, see 'def'.
251
252        if {![llength $cargs]} {
253            return [eval $m]
254        } else {
255            # Explanation: First linsert constructs 'linsert $m end {*}$cargs',
256            # which the inner eval transforms into '{*}$m {*}$cargs', which at
257            # last is run by the outer eval.
258            return [eval [eval [linsert $cargs 0 linsert $m end]]]
259        }
260    }
261
262    # ### ### ### ######### ######### #########
263    ## Input manipulation
264
265    # Get next word from the input (shift)
266    method next {} {
267        set w       [lindex $program 0]
268        set program [lrange $program 1 end]
269        return $w
270    }
271
272    # Peek at the next word in the input
273    method peek {} {
274        return [lindex $program 0]
275    }
276
277    # Retrieve the whole current program
278    method peekall {} {
279        return $program
280    }
281
282    # Replace the current programm
283    method replace {args} {
284        set program $args
285        return
286    }
287    method replacel {alist} {
288        set program $alist
289        return
290    }
291
292    # Insert words into the input stream.
293    method insert {at args} {
294        set program [eval [linsert $args end linsert $program $at]]
295        return
296    }
297    method insertl {at alist} {
298        set program [eval [linsert $alist end linsert $program $at]]
299        return
300    }
301
302    # <=> insert 0
303    method push {args} {
304        set program [eval [linsert $args end linsert $program 0]]
305        return
306    }
307    method pushl {alist} {
308        set program [eval [linsert $alist end linsert $program 0]]
309        return
310    }
311
312    # <=> insert end
313    method add {args} {
314        set program [eval [linsert $args end linsert $program end]]
315        return
316    }
317    method addl {alist} {
318        set program [eval [linsert $alist end linsert $program end]]
319        return
320    }
321
322    ##
323    # ### ### ### ######### ######### #########
324}
325
326# ### ### ### ######### ######### #########
327##
328
329# Macro to declare the method of a component as proc. We use this
330# later to make access to a WIP processor simpler (no need to write
331# the component reference on our own). And no, this is not the same as
332# the standard delegation. Doing that simply replaces the component
333# name in the call with '$self'. We remove the need to have this
334# written in the call.
335
336snit::macro wip::methodasproc {var method suffix} {
337    proc $method$suffix {args} [string map [list @v@ $var @m@ $method] {
338        upvar 1 {@v@} dst
339        return [eval [linsert $args 0 $dst {@m@}]]
340    }]
341}
342
343# ### ### ### ######### ######### #########
344## Ready
345
346# ### ### ### ######### ######### #########
347##
348
349# Macro to install most of the boilerplate needed to setup and use a
350# WIP. The only thing left is to call the method 'wip_setup' in the
351# constructor of the class using WIP. This macro allows the creation
352# of multiple wip's, through custom suffices.
353
354snit::macro wip::dsl {{suffix {}}} {
355    if {$suffix ne ""} {set suffix _$suffix}
356
357    # Instance state, wip processor used to run the language
358    component wip$suffix
359
360    # Standard method to create the processor component. The user has
361    # to manually add a call of this method to the constructor.
362
363    method wip${suffix}_setup {} [string map [list @@ $suffix] {
364        install {wip@@} using wip "${selfns}::wip@@" $self
365    }]
366
367    # Procedures for easy access to the processor methods, without
368    # having to use self and wip. I.e. special delegation.
369
370    foreach {p} {
371        add     addl    def
372        defd    defdva  defl    deflva
373        insert  insertl replace replacel
374        push    pushl   run     runl
375        next    peek    peekall run_next
376        run_next_until  run_next_while
377    } {
378        wip::methodasproc wip$suffix $p $suffix
379    }
380    return
381}
382
383# ### ### ### ######### ######### #########
384## Ready
385
386package provide wip 1.0
Note: See TracBrowser for help on using the repository browser.