Version 13 of tclparser

Updated 2018-01-13 10:19:41 by APN

An extension for Tcl, written in C, that lets Tcl scripts access Tcl's own parser via the parse command.

As a package, the name is parser.

aspect maintains a fossil repo of tclparser by itself.

The module originated in TclPro, whose source code is available from sourceforge CVS

Building

  fossil clone https://chiselapp.com/user/aspect/repository/tclparser tclparser.fossil
  mkdir -p tclparser/build
  cd tclparser
  fossil open ../tclparser.fossil
  cd build
  ../configure --prefix=/home/tcl --with-tcl=/home/tcl/lib
  make all install

  cvs -d:pserver:[email protected]:/cvsroot/tclpro login
  cvs -z3 -d:pserver:[email protected]:/cvsroot/tclpro co tclparser
  mkdir tclparser/build
  cd tclparser/build
  ../configure --prefix=/home/tcl --with-tcl=/home/tcl/lib
  make
  make install

Despite CVS history showing no activity since 2007, this builds cleanly against 8.6.4. That's a stable interface!

Using

As the manual explains, it exposes one command parse $type $text $range, which returns a structure similar to tcltest's testparser, but a bit more convenient for script manipulation. Like tcltest::testparser, this is a lightweight wrapper around functions in tclParse.c .

Parsing Expressions

A simple example to turn an expression into namespace evalable code:

package require parser 

proc expr2tcl {expr {parse ""}} {
    if {$parse eq ""} {
        set parse [parse expr $expr {0 end}]
    }
    lassign $parse type range parts
    lassign $range min max
    incr max $min
    incr max -1
    set text [string range $expr $min $max]
    set result ""
    switch $type {
        subexpr {
            set result [join [lmap part $parts {expr2tcl $expr $part}] " "]
            if {[lindex $parts 0 0] eq "operator"} {
                return \[$result\]
            } else {
                return $result
            }
        }
        default {
            return $text
        }
    }
}

# % puts [expr2tcl {sin($x)+4*$x-$x**(pow($x,2))}]
# [- [+ [sin $x] [* 4 $x]] [** $x [pow $x 2]]]

Parsing Scripts and Commands

To parse a script, repeatedly call parse command. Its result is a 4-tuple {commentRange commandRange restRange parseTree}. parseTree is an interesting structure that breaks down the words in the command, and the rest are just index pairs as illustrated in the below work-alike of scriptSplit .

package require parser

proc stringRange {string start step} {
    tailcall string range $string $start [expr {$start+$step}]
}

proc ss {script} {
    set restRange {0 end}
    while {1} {
        lassign [parse command $script $restRange] commentRange commandRange restRange tree
        set comment [stringRange $script {*}$commentRange]
        set command [stringRange $script {*}$commandRange]
        if {$command eq ""} break
        #puts "Parsed a command {$tree} {$command}"
        lappend result $command
    }
    return $result
}

Note that the command terminator (newline or semicolon) is included in commandRange. But only if it is present. This is discussed somewhere else.

APN Minor comment on the above use of string range. The expr is not needed as string range arguments can include + and - arithmetic operators. Also, the use of tailcall here does nothing useful other than slow down the procedure.


See also:

  • parsetcl (pure-Tcl alternative, different API)
  • scriptSplit uses introspection to get a similar result