wip.tcl

Go to the documentation of this file.
00001 /*  ### ### ### ######### ######### #########*/
00002 /** 
00003  * (c) 2007 Andreas Kupries.
00004  */
00005 
00006 /*  WIP = Word Interpreter (Also a Work In Progress :). Especially while*/
00007 /*  it is running :P*/
00008 
00009 /*  Micro interpreter for lists of words. Domain specific languages*/
00010 /*  based on this will have a bit of a Forth feel, with the input stream*/
00011 /*  segmented into words and any other structuring left to whatever*/
00012 /*  language. Note that we have here in essence only the core dispatch*/
00013 /*  loop, and no actual commands whatsoever, making this definitely only*/
00014 /*  a Forth feel and not an actual Forth.*/
00015 
00016 /*  The idea is derived from Colin McCormack's treeql processor,*/
00017 /*  modified to require less boiler plate within the command*/
00018 /*  implementations, at the expense of, likely, execution speed. In*/
00019 /*  addition the interface between processor core and commands is more*/
00020 /*  complex too.*/
00021 
00022 /*  ### ### ### ######### ######### #########*/
00023 /*  Requisites*/
00024 
00025 package require Tcl 8.4
00026 
00027 /*  For Tcl 8.{3,4} only snit1 of a suitable patchlevel is possible.*/
00028 package require snit 1.3
00029 
00030 /*  The run_next_* methods use set operations (x in set)*/
00031 package require struct::
00032 
00033 # ### ### ### ######### ######### #########
00034 ## API =  & Implementation
00035 
00036 snit::type wip {
00037 
00038     /*  ### ### ### ######### ######### #########*/
00039     /*  API*/
00040 
00041     constructor           {e}       {} ; /*  create processor*/
00042 
00043     /*  Defining commands and where they dispatch to.*/
00044     ret  def            (type name , optional cp ={)} {} ; # Define a DSL command.
00045     method defl           {names}        {} ; # Def many, simple names (cp = name)
00046     method defd           {dict}         {} ; # s.a. name/cp dict
00047     method deflva         {args}         {} ; # s.a. defl, var arg form
00048     method defdva         {args}         {} ; # s.a. defd, var arg form
00049 
00050     # Execution of word lists.
00051     method runl           {alist}   {} ; # execute list of words
00052     method run            {args}    {} ; # ditto, words as varargs
00053     method run_next       {}        {} ; # run the next command in the input.
00054     method run_next_while {accept}  {} ; # s.a., while acceptable command
00055     method run_next_until {reject}  {} ; # s.a., until rejectable command
00056 
00057     # Manipulation of the input word list.
00058     method peek           {}        {} ; # peek at next word in input
00059     method next           {}        {} ; # pull next word from input
00060     method insert         {at args} {} ; # insert words back into the input
00061     method push           {args}    {} ; # ditto, at == 0
00062 
00063     # ### ### ### ######### ######### #########
00064     ## Processor construction.
00065 
00066     constructor {e args} {
00067     if {$e eq ""} {
00068         return -code error "No engine specified"
00069     }
00070      engine =  $e
00071     $self Definitions $args
00072     return
00073     }
00074 
00075     ret  Definitions (type alist) {
00076     # args = series of 'def name' and 'def name cp' statements.
00077     # The code to handle them is in essence a WIP too, just
00078     # hardcoded, as state machine.
00079 
00080     set state expect-def
00081     set n  {}
00082     set cp {}
00083     foreach a $alist {
00084         if {$state eq "expect-def"} {
00085         if {$a ne "def"} {
00086             return -code error "Expected \"def\", got \"$a\""
00087         }
00088         set state get-name
00089         } elseif {$state eq "get-name"} {
00090         set name $a
00091         set state get-cp-or-def
00092         } elseif {$state eq "get-cp-or-def"} {
00093         # This means that 'def' cannot be a command prefix for
00094         # DSL command.
00095         if {$a eq "def"} {
00096             # Short definition, name only, completed.
00097             $self def $name
00098             # We already have the first word of the next
00099             # definition here, name is coming up next.
00100             set state get-name
00101         } else {
00102             # Long definition, name + cp, completed.
00103             $self def $name $a
00104             # Must be followed by the next definition.
00105             set state expect-def
00106         }
00107         }
00108     }
00109     if {$state eq "get-cp-or-def"} {
00110         # Had a short definition last, now complete.
00111         $self def $name
00112     } elseif {$state eq "get-name"} {
00113         # Incomplete definition at the end, bogus
00114         return -code error "Incomplete definition at end, name missing."
00115     }
00116     return
00117     }
00118 
00119     /*  ### ### ### ######### ######### #########*/
00120     /*  Processor state*/
00121     /*  Handle of the object incoming commands are dispatched to.*/
00122     /*  The currently active DSL code, i.e. word list.*/
00123 
00124     variable engine  {}      ; /*  command*/
00125     variable program {}      ; /*  list (string)*/
00126     variable arity -array {} ; /*  array (command name -> command arity)*/
00127     variable cmd   -array {} ; /*  array (command name -> method cmd prefix)*/
00128 
00129     /*  ### ### ### ######### ######### #########*/
00130     /*  API: DSL definition*/
00131 
00132     /*  DSL words map to method-prefixes, i.e. method names + fixed*/
00133     /*  arguments. We store them with the engine already added in front*/
00134     /*  to make them regular command prefixes. No 'mymethod' however,*/
00135     /*  that works only in engine code itself, not form the outside.*/
00136 
00137     ret  def (type name , optional mp ={)} {
00138     if {$mp eq {}} {
00139         /*  Derive method-prefix from DSL word.*/
00140          mp =  [list $name]
00141          m =   $name
00142          n =  0
00143 
00144     } else {
00145         /*  No need to check for an empty method-prefix. That cannot*/
00146         /*  happen, as it is diverted, see above.*/
00147 
00148          m =  [lindex $mp 0]
00149          n =  [expr {[llength $mp]-1}]
00150     }
00151 
00152     /*  Get method arguments, check for problems.*/
00153      a =  [$engine info args $m]
00154     if {[lindex $a end] eq "args"} {
00155         return -code error "Unable to handle Tcl varargs"
00156     }
00157 
00158     /*  The arity of the command is number of required arguments,*/
00159     /*  with compensation for those already covered by the*/
00160     /*  method-prefix.*/
00161 
00162      cmd = ($name)   [linsert $mp 0 $engine]
00163      arity = ($name) [expr {[llength $a] - $n}]
00164     return
00165     }
00166 
00167     ret  deflva (type args)  { $self defl $args ; return }
00168     ret  defdva (type args)  { $self defd $args ; return }
00169     ret  defl   (type names) { foreach n $names { $self def $n } ; return }
00170     ret  defd   (type dict)  {
00171     if {[llength $dict]%2==1} {
00172         return -code error "Expected a dictionary, got \"$dict\""
00173     }
00174     foreach {name mp} $dict {
00175         $self def $name $mp
00176     }
00177     return
00178     }
00179 
00180     /*  ### ### ### ######### ######### #########*/
00181     /*  API: DSL execution*/
00182     /* */
00183     /*  Consider moving the core implementation into procs, to reduce*/
00184     /*  call overhead*/
00185 
00186     ret  run (type args) {
00187     return [$self runl $args]
00188     }
00189 
00190     ret  runl (type alist) {
00191     # Note: We are saving the current program and restore it
00192     # afterwards, this handles the possibility that this is a
00193     # recursive call into the dispatcher.
00194     set saved $program
00195     set program $alist
00196     set r {}
00197     while {[llength $program]} {
00198         set r [$self run_next]
00199     }
00200     set program $saved
00201     return $r
00202     }
00203 
00204     ret  run_next_while (type accept) {
00205     set r {}
00206     while {[struct::set contains $accept [$self peek]]} {
00207         set r [$self run_next]
00208     }
00209     return $r
00210     }
00211 
00212     ret  run_next_until (type reject) {
00213     set r {}
00214     while {![struct::set contains $reject [$self peek]]} {
00215         set r [$self run_next]
00216     }
00217     return $r
00218     }
00219 
00220     ret  run_next () {
00221     # The first word in the list is the current command. Determine
00222     # the number of its fixed arguments. This also checks command
00223     # validity in general.
00224 
00225     set c [lindex $program 0]
00226     if {![info exists arity($c)]} {
00227         return -code error \
00228         "Unknown command \"$c\""
00229     }
00230 
00231     set n $arity($c)
00232     set m $cmd($c)
00233 
00234     # Take the fixed arguments from the input as well.
00235 
00236     set cargs [lrange $program 1 $n]
00237     incr n
00238 
00239     # Remove the command to dispatch, and its fixed arguments from
00240     # the program. This is done before the dispatch so that the
00241     # command has access to the true current state of the input.
00242 
00243     set program [lrange $program $n end]
00244 
00245     # Now run the command with its arguments. Commands needing
00246     # more than the declared fixed number of arguments are
00247     # responsible for reading them from input via the method
00248     # 'next' provided by the processor core.
00249 
00250     # Note: m already has the engine at the front, it was stored
00251     # that way, see 'def'.
00252 
00253     if {![llength $cargs]} {
00254         return [eval $m]
00255     } else {
00256         # Explanation: First linsert constructs 'linsert $m end {*}$cargs',
00257         # which the inner eval transforms into '{*}$m {*}$cargs', which at
00258         # last is run by the outer eval.
00259         return [eval [eval [linsert $cargs 0 linsert $m end]]]
00260     }
00261     }
00262 
00263     /*  ### ### ### ######### ######### #########*/
00264     /*  Input manipulation*/
00265 
00266     /*  Get next word from the input (shift)*/
00267     ret  next () {
00268     set w       [lindex $program 0]
00269     set program [lrange $program 1 end]
00270     return $w
00271     }
00272 
00273     /*  Peek at the next word in the input*/
00274     ret  peek () {
00275     return [lindex $program 0]
00276     }
00277 
00278     /*  Retrieve the whole current program*/
00279     ret  peekall () {
00280     return $program
00281     }
00282 
00283     /*  Replace the current programm*/
00284     ret  replace (type args) {
00285     set program $args
00286     return
00287     }
00288     ret  replacel (type alist) {
00289     set program $alist
00290     return
00291     }
00292 
00293     /*  Insert words into the input stream.*/
00294     ret  insert (type at , type args) {
00295     set program [eval [linsert $args end linsert $program $at]]
00296     return
00297     }
00298     ret  insertl (type at , type alist) {
00299     set program [eval [linsert $alist end linsert $program $at]]
00300     return
00301     }
00302 
00303     /*  <=> insert 0*/
00304     ret  push (type args) {
00305     set program [eval [linsert $args end linsert $program 0]]
00306     return
00307     }
00308     ret  pushl (type alist) {
00309     set program [eval [linsert $alist end linsert $program 0]]
00310     return
00311     }
00312 
00313     /*  <=> insert end*/
00314     ret  add (type args) {
00315     set program [eval [linsert $args end linsert $program end]]
00316     return
00317     }
00318     ret  addl (type alist) {
00319     set program [eval [linsert $alist end linsert $program end]]
00320     return
00321     }
00322 
00323     /** 
00324      * ### ### ### ######### ######### #########
00325  */
00326 }
00327 
00328 /*  ### ### ### ######### ######### #########*/
00329 /** 
00330  */
00331 
00332 /*  Macro to declare the method of a component as proc. We use this*/
00333 /*  later to make access to a WIP processor simpler (no need to write*/
00334 /*  the component reference on our own). And no, this is not the same as*/
00335 /*  the standard delegation. Doing that simply replaces the component*/
00336 /*  name in the call with '$self'. We remove the need to have this*/
00337 /*  written in the call.*/
00338 
00339 snit::macro wip::ret asproc (type var , type method , type suffix) {
00340     proc $method$suffix {args} [string map [list @v@ $var @m@ $method] {
00341     upvar 1 {@v@} dst
00342     return [eval [linsert $args 0 $dst {@m@}]]
00343     }]
00344 }
00345 
00346 /*  ### ### ### ######### ######### #########*/
00347 /*  Ready*/
00348 
00349 /*  ### ### ### ######### ######### #########*/
00350 /** 
00351  */
00352 
00353 /*  Macro to install most of the boilerplate needed to setup and use a*/
00354 /*  WIP. The only thing left is to call the method 'wip_setup' in the*/
00355 /*  constructor of the class using WIP. This macro allows the creation*/
00356 /*  of multiple wip's, through custom suffices.*/
00357 
00358 snit::macro wip::dsl {{suffix {}}} {
00359     if {$suffix ne ""} { suffix =  _$suffix}
00360 
00361     /*  Instance state, wip processor used to run the language*/
00362     component wip$suffix
00363 
00364     /*  Standard method to create the processor component. The user has*/
00365     /*  to manually add a call of this method to the constructor.*/
00366 
00367     ret  wip$(type suffix)_setup {} [string map [list @@ $suffix] {
00368     install {wip@@} using wip "${selfns}::wip@@" $self
00369     }]
00370 
00371     /*  Procedures for easy access to the processor methods, without*/
00372     /*  having to use self and wip. I.e. special delegation.*/
00373 
00374     foreach {p} {
00375     add addl    def
00376     defd    defdva  defl    deflva
00377     insert  insertl replace replacel
00378     push    pushl   run runl
00379     next    peek    peekall run_next
00380     run_next_until  run_next_while
00381     } {
00382     wip::ret asproc wip$suffix $p $suffix
00383     }
00384     return
00385 }
00386 
00387 # ### ### ### ######### ######### #########
00388 ## Ready
00389 
00390 package provide wip 1.0
00391 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1