Richard Suchenwirth 2008-03-17 - Once again, I had some waiting time, and wanted to experiment with Tcl - in this case some toys to
What is not covered is the precedence of operators. The code below associates to the left if not parenthesized, so 2+3*4 gives 20... On the other hand, I hope it is nicely readable, with the e.g. tests/comments sprinkled in :^)
#-------------- Testing framework first, so we can test immediately set verbose [expr {[lsearch $argv -v] >= 0}] set N 0 set Nfail 0 proc e.g. {cmd -> expected} { incr ::N catch {uplevel 1 $cmd} res if {$res ne $expected} { puts [list ($::N) $cmd -> $res ***** expected $expected] incr ::Nfail } elseif $::verbose {puts [list ($::N) $cmd -> $res]} } #------------------------------------------------------- real stuff proc typeof char { switch -- $char { 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - . {return digit} + - "-" - * - / - ( - ) {return op} default {error "bad character $char"} } } e.g. {typeof 7} -> digit e.g. {typeof .} -> digit e.g. {typeof *} -> op e.g. {typeof ?} -> {bad character ?} #---------------------------------------------------------------- proc tokenize str { set res "" set previous "" set parens 0 foreach char [split $str ""] { if {$char eq "("} {incr parens; append res " "} if {$char eq ")"} {incr parens -1} if {$char eq " "} continue set type [typeof $char] if {$previous ne "" && $type ne $previous} {append res " "} set previous $type append res $char if {$char eq ")"} {append res " "} } if $parens {error "unmatched parens $parens"} string trim $res } e.g. {tokenize 2+3} -> {2 + 3} e.g. {tokenize "2 + 3"} -> {2 + 3} e.g. {tokenize "2 + (3)"} -> {2 + ( 3 )} e.g. {tokenize 12+43} -> {12 + 43} e.g. {tokenize (} -> {unmatched parens 1} e.g. {tokenize )} -> {unmatched parens -1} #----------------------------------------------------------------- Stack proc pop {{_stack ""}} { if {$_stack eq ""} {set _stack ::S} ;# default: global stack upvar 1 $_stack stack K [lindex $stack end] [set stack [lrange $stack 0 end-1]] } proc K {a b} {set a} proc push what {lappend ::S $what} e.g. {push hello; set S} -> hello e.g. {pop; set S} -> {} #----------------------------------------------------------------- Parser proc parse tokens { #puts [info level 0] set res {} set opstack {} set braces 0 set recurs {} foreach token $tokens { if $braces { lappend recurs $token if {$token eq "("} {incr braces} if {$token eq ")"} { incr braces -1 if {$braces == 0} { eval lappend res [parse [lrange $recurs 0 end-1]] set recurs {} } } } else { if [string is double -strict $token] { lappend res [list push $token] if {[llength $opstack]} {lappend res [pop opstack]} } elseif {$token eq "("} { incr braces } elseif {[typeof $token] eq "op"} { lappend opstack $token } } #puts token:$token,braces:$braces,recurs:$recurs } while {[llength $opstack]} {lappend res [pop opstack]} set res } e.g. {parse "2 + 3"} -> {{push 2} {push 3} +} e.g. {parse "2 + 3 + 4"} -> {{push 2} {push 3} + {push 4} +} e.g. {parse "( 2 + 3 ) * 4"} -> {{push 2} {push 3} + {push 4} *} #---------------------------------------------------------------- foreach op {+ - * /} { proc $op {} "set a \[pop\]; push \[expr {\[pop\] $op \$a}\]" } e.g. {info body +} -> {set a [pop]; push [expr {[pop] + $a}]} #----------------------------------------------------------- RPN evaluator proc rpn cmdlist { if $::verbose {puts $cmdlist} global S foreach cmd $cmdlist {eval $cmd} if {[llength $S] != 1} {error "bad stack: $S"} pop } e.g. {rpn {{push 6} {push 7} *}} -> 42 #---------------------------------------------------------------- proc i2r {expr} {rpn [parse [tokenize $expr]]} e.g. {i2r 2+3} -> 5 e.g. {i2r 2/3} -> 0 e.g. {i2r 1/2.} -> 0.5 e.g. {i2r 1./2} -> 0.5 e.g. {i2r 2/0} -> "divide by zero" e.g. {i2r 2/3.} -> 0.666666666667 e.g. {i2r (2+3)*4} -> 20 e.g. {i2r 2+3*4} -> 20 ;# yes... left-associative, no precedence e.g. {i2r 2+(3*4)} -> 14 e.g. {i2r 2+3+4} -> 9 e.g. {i2r (1+2)*(3+4)} -> 21 e.g. {i2r 2*3+4} -> 10 e.g. {i2r (2*3)+4} -> 10 e.g. {i2r (2*3)+(4)} -> 10 e.g. {i2r 3*(3+4)} -> 21 e.g. {i2r {3 + ( 4 * ( 5 + 6 ) )}} -> 47 #------------------------------ Final tally incr N -1 ;# last call to e.g. will incr again puts --- e.g. {list $Nfail / $N = [format %.2f [expr 100.*$Nfail/$N]] %} -> 0 puts "" foreach arg $argv {e.g. $arg -> ?}