- 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.