- It does not resolve function calls yet
- It does not deal with array elements yet
- As constants may taken any form or shape, I think supporting general constants will be nigh to impossible
- You can define a new command that takes an expression like {$a+($b-$c)/$d} and transforms that into a prefix form so that you can simply define specific commands for the +, -, * and / operations appropriate for the data stored in the variables a, b, c, d.
- The command is fairly efficient as it will need to analyse an expression only once and then caches the result
::ParseExpressions::makeExpr cexpr {}creates a command cexpr that interprets the variables as complex numbers. The (imported) +,-, /, and * procedures do the dirty work:
cexpr {$a+($b-$c)/$d} ==> uplevel 1 {+ $a [/ [- $b $c] $d]} ==> the answerIn a very similar way one could:- Create a command to deal with vector and matrix computations
- Create a command to deal with arbitrary precision reals
- Create a command to deal with interval arithmetic
- ...
# parse_expr.tcl --
# Parse an arbitrary arithmetic expression
# and turn it into an equivalent prefix
# expression.
#
namespace eval ::ParseExpressions {
namespace export parseExpr
}
# TranslateLexeme --
# Translate the lexeme (operator or function name)
# Arguments:
# lexeme Lexeme to be translated
# translation List of expression-function pairs
# Result:
# Next lexeme
#
proc ::ParseExpressions::TranslateLexeme {lexeme translation} {
set idx [lsearch $translation $lexeme]
if { $idx >= 0 && $idx%2 == 0 } {
return [lindex $translation [incr idx]]
} else {
return $lexeme
}
}
# GetLexeme --
# Split the expression in lexemes
# Arguments:
# token_list List of tokens
# Result:
# Next lexeme
#
proc ::ParseExpressions::GetLexeme {token_list} {
#
# Simple for the moment :)
#
#puts "lexeme: [lindex $token_list 0] -- [lrange $token_list 1 end]"
return [lindex $token_list 0]
}
# ConsumeLexeme --
# Remove the current lexeme and return a new partial expression
# Arguments:
# token_list List of tokens
# Result:
# New partial expression
#
proc ::ParseExpressions::ConsumeLexeme {token_list} {
#
# Simple for the moment :)
#
#puts "consume: [lindex $token_list 0] -- [lrange $token_list 1 end]"
return [lrange $token_list 1 end]
}
# ParsePrimaryExpr --
# Parse primary expressions
# Arguments:
# token_list List of tokens
# translation List of expression-function pairs
# Result:
# Parsed expression and remaining list
#
proc ::ParseExpressions::ParsePrimaryExpr {token_list translation} {
#puts "Primary - $token_list"
#
# Simple for the moment :)
#
#puts "Primary - result: [lindex $token_list 0]"
set lexeme [GetLexeme $token_list]
if { $lexeme == "(" } {
set token_list [ConsumeLexeme $token_list]
foreach {result token_list} [ParseAddExpr $token_list $translation] {break}
#puts "Returned tokenlist: $token_list"
set lexeme [GetLexeme $token_list]
set token_list [ConsumeLexeme $token_list]
if { $lexeme != ")" } { error "No closing parenthesis" }
return [list $result $token_list]
} elseif { $lexeme == ")" } {
return [list {} $token_list]
} else {
return [list [lindex $token_list 0] [lrange $token_list 1 end]]
}
}
# ParseMultiplyExpr --
# Parse multiply-like expressions
# Arguments:
# token_list List of tokens
# translation List of expression-function pairs
# Result:
# Parsed expression and remaining list
#
proc ::ParseExpressions::ParseMultiplyExpr {token_list translation} {
#puts "Multiply - $token_list"
set result ""
foreach {left token_list} [ParsePrimaryExpr $token_list $translation] {break}
set hasop 0
set lexeme [GetLexeme $token_list]
while { $lexeme == "*" || $lexeme == "/" } {
set token_list [ConsumeLexeme $token_list]
set lexeme [TranslateLexeme $lexeme $translation]
foreach {right token_list} [ParseMultiplyExpr $token_list $translation] {break}
if { ! $hasop } {
set hasop 1
set result "\[$lexeme $left $right\]"
} else {
set result "\[$lexeme $result $right\]"
}
set lexeme [GetLexeme $token_list]
}
if { ! $hasop } {
set result "$left"
}
#puts "Multiply - result: $result"
return [list $result $token_list]
}
# ParseAddExpr --
# Parse add-like expressions
# Arguments:
# token_list List of tokens
# translation List of expression-function pairs
# Result:
# Parsed expression and remaining list
#
proc ::ParseExpressions::ParseAddExpr {token_list translation} {
#puts "Add - $token_list"
set result ""
foreach {left token_list} [ParseMultiplyExpr $token_list $translation] {break}
set hasop 0
set lexeme [GetLexeme $token_list]
while { $lexeme == "+" || $lexeme == "-" } {
set token_list [ConsumeLexeme $token_list]
set lexeme [TranslateLexeme $lexeme $translation]
foreach {right token_list} [ParseMultiplyExpr $token_list $translation] {break}
if { ! $hasop } {
set hasop 1
set result "\[$lexeme $left $right\]"
} else {
set result "\[$lexeme $result $right\]"
}
set lexeme [GetLexeme $token_list]
}
if { ! $hasop } {
set result "$left"
}
#puts "Add - result: $result"
return [list $result $token_list]
}
# ParseExpr --
# Turn a list of tokens into an expression tree
# Arguments:
# token_list List of tokens
# translation List of expression-function pairs
# Result:
# Nested list representing the expression tree
#
proc ::ParseExpressions::ParseExpr {token_list translation} {
variable operators
if { $token_list == {} } {
return {}
}
return [string range [lindex [ParseAddExpr $token_list $translation] 0] 1 end-1]
}
# TokenizeExpr --
# Split an expression in tokens for further processing
# Arguments:
# string String holding the expression
# Returns:
# List of tokens
#
proc ::ParseExpressions::TokenizeExpr {string} {
set result {}
set name 0
set token ""
set op ""
set brackets 0
foreach c [split $string ""] {
switch -regexp -- $c {
{\$} {
if { $name } {
return -code error "\$ follows a variable name without an operator"
}
set op ""
set name 1
append token $c
}
{[a-zA-Z_0-9]} {
set op ""
append token $c
}
{ } {
# Skip spaces ...
}
{\.} {
# Append to integer numbers only
if { [string is integer $token] || $token == "" } {
set op ""
append token $c
} else {
return -code error ". follows a variable name without an operator"
}
}
{[-+*/]} {
if { $token != "" } {
lappend result $token
set token ""
}
if { $op != "" } {
puts ">>> $result"
return -code error "Two operators without intervening operands"
}
set op $c
lappend result $c
set name 0
}
{[(]} {
incr brackets
if { $name != 0 } {
return -code error "( preceeded by a variable name or number"
}
set op ""
lappend result $c
}
{[)]} {
incr brackets -1
if { $brackets < 0 } {
return -code error "too many closing brackets"
}
if { $token != "" } {
lappend result $token
set token ""
}
lappend result $c
}
}
}
if { $token != "" } {
lappend result $token
}
if { $brackets > 0 } {
return -code error "opening brackets not balanced with closing brackets"
}
return $result
}
# makeExpr --
# Make an expression evalutating procedure
# Arguments:
# name Name of the procedure
# translation Translation of the operators into functions (list
# of operator-function name pairs)
# Returns:
# Nothing
# Side effects:
# New procedure created in the caller's namespace
#
proc ::ParseExpressions::makeExpr {name translation} {
set ns [uplevel 1 {namespace current}]
proc ${ns}::$name expr [string map [list TR $translation NAME $name] {
variable Expr_NAME
if { ![info exists Expr_NAME($expr)] } {
set Expr_NAME($expr) [::ParseExpressions::ParseExpr [::ParseExpressions::TokenizeExpr $expr] {TR}]
}
uplevel 1 $Expr_NAME($expr)
}]
}
# main --
# Testing the stuff
#
proc add {a b} {
return "$a+$b"
}
proc sub {a b} {
return "$a-$b"
}
puts [::ParseExpressions::ParseExpr {1 + 2} {}]
puts [::ParseExpressions::ParseExpr {1 + 2 + 3} {}]
puts [::ParseExpressions::ParseExpr {1 + 2 * 3} {}]
puts [::ParseExpressions::ParseExpr {1 * 2 + 3} {}]
puts [::ParseExpressions::ParseExpr {1 - 2 - 3 - 4 * 5 } {}]
puts [::ParseExpressions::ParseExpr {1 - ( 2 - 3 ) - 4 * 5 } {}]
puts [::ParseExpressions::ParseExpr {1} {}]
puts [::ParseExpressions::ParseExpr {1 - ( ( 2 - 3 ) / ( 4 * 5 ) ) } {}]
puts [::ParseExpressions::TokenizeExpr {$a + $b * ( $c - $d)}]
puts [::ParseExpressions::TokenizeExpr {($along2 * $bstep )+ 1 / ( $c - $d + 1.0)}]
# Incorrect expressions:
#puts [::ParseExpressions::TokenizeExpr {($a++$b * $bstep )+ 1 / ( $c - $d + 1.0)}]
#puts [::ParseExpressions::TokenizeExpr {($a$b * $bstep )+ 1 / ( $c - $d + 1.0)}]
# TODO:
# - function calls, array elements, ** operator
# - unary operators
::ParseExpressions::makeExpr chexpr {+ add - sub}
set a "AA"
set b "BB"
set c "CC"
set d "DD"
puts [chexpr $a+$b-($c+$d)]
# More serious now ....
#
package require math::complexnumbers
namespace import ::math::complexnumbers::*
::ParseExpressions::makeExpr cexpr {}
set a [complex 1 1]
set b [complex 2 1]
set c [conj $a]
puts "a + b = [cexpr {$a+$b}]"
puts "a + b*a = [cexpr {$a+$b*$a}]"
puts "a / c = [cexpr {$a/$c}]"AM [expr] can be exploited in other ways too: Using expr on lists
arjen - 2010-02-16 02:50:58I have picked up this idea again and added unary operations. That new code is not on the Wiki yet. I want to add function calls to the mix first.Basically, with my project for wrapping LAPACK routines I am becoming increasingly interested in working with lists of numerical data (vectors and matrices). And an [expr] command that works nicely with them would be very convenient.

