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

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1