source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/wip/wip2.tcl @ 8597867d

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