Sarnold 2009-09-20 - Yet another week-end fun project: parsing S-EXP to Tcl.
Usage:
lispy::parse string
% source lispy.tcl % lispy::parse {puts (* (math.atan -1) 4)} puts [expr {atan(-1)*4}]
Lispy syntax
Each command starts with a command name and continues with arguments. A simple word (which starts with a letter or a colon) is taken as value (token becomes $token) if it is not the first word of the command. As in Lisp, parentheses match nested commands:
add (mul x y) (mul z t) -> add [mul $x $y] [mul $z $t]
To give expressions, you can use the following commands/functions:
* + / - % ** < <= == != > >= >> << & && | || ^ ~ ! lt? gt? eq? ne? in? ni? ge? le?
and the math functions defined by expr are accessed via math.funcname
(+ x (math.sin y)) => expr {$x+sin(y)}
Expressions expected by commands such as if, while, etc. are to be prepended with a question mark:
(if ?(< x 0) (begin (puts "x is negative")))
Numbers are put verbatim
1.2 -> 1.2
Options are put verbatim, as with command names starting with special chars.
-textvariable ++ *
To start the command with a variable value, you will have to put a dollar sign in front of it.
$widget 'add 'circle 1 1 10 10 -> $widget add circle 1 1 10 10
String interpolation is possible with double-quoted strings which are put verbatim.
puts "Hello my $friend"
Lists are built with (single) quotes in front of parenthesized expressions, and can be nested.
lindex '(1 2 3 4) 0 -> lindex {1 2 3 4} 0
Blocks are built with the special prefix "begin":
if ?{> d 0) (begin (puts "Success")) -> if {$d > 0} { puts "Success" }
Comments are allowed but they start with semicolons and end at the end of line.Commands are not required to fit on one line.
package provide lispy 0.4 namespace eval lispy {} proc lispy::hasescape string { expr {[string length $string] - [string length [string trimright $string \\]] % 2 == 1} } proc lispy::lexeme code { foreach {regex token} { {\-?[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?} number {".*"} string {[a-zA-Z_:][A-Za-z0-9_:]*} var {\$[a-zA-Z_:][A-Za-z0-9_:]*} dvar {'[a-zA-Z_][A-Za-z0-9_]*} tag {[+\-\*/\!=<>%\|&A-Aa-z_:\.][+\-\*/\!=<>%\|&A-Aa-z_:\.0-9]*} cmd {'\(} list {\?\(} expr {\(begin} begin {\(} open {\)} close {;.*\n} comment {[\t ]+} blank } { if {[regexp ^$regex $code match]} { if {$token eq "string"} { while {[hasescape [string range $match 0 end-1]]} { set rest [string range $code [string length $match] end] set i [string first $rest \"] if {$i < 0} {error "unfinished string"} append match [string range $rest 0 $i] } return [list $token $match] } if {$token in {close comment blank expr begin open list}} {return [list $token $match]} if {$code ne $match && [string index $code [string length $match]] ni [split "); \t" ""]} continue return [list $token $match] } } error "invalid syntax at: '$code'" } proc lispy::ismath x { if {$x in {* + / - % ** < <= == != > >= >> << & && | || ^ ~ ! lt? gt? eq? ne? in? ni? ge? le?}} { return yes } return no } proc lispy::ismathfun x { set prefix math. if {[string range $x 0 4] eq $prefix} {return yes} return no } proc lispy::math x { if {$x in {lt? gt? eq? ne? in? ni? ge? le?}} {return [string range $x 0 1]} if {[string range $x 0 4] eq "math."} {return [string range $x 5 end]} set x } proc lispy::make-parser {name body} { proc parse-$name var [string map [list %body [list $body]] { upvar 1 code $var set res "" while {$code ne ""} { foreach {tag string} [lexeme $code] break set code [string range $code [string length $string] end] if {$tag eq "comment"} continue switch -- $tag %body } return $res }] } lispy::make-parser command { blank - number - string - dvar { # put verbatim append res $string return "$res[parse-arguments code]" } var - cmd { if {[lispy::ismath $string]} { append res "expr {[join [parse-expr code] [lispy::math $string]]}" return $res } if {[lispy::ismathfun $string]} { append res "expr {[lispy::math $string]([join [parse-expr code] ", "])}" return $res } # put verbatim append res $string return "$res[parse-arguments code]" } expr { error "expression not allowed as command" } tag { append res [string range $string 1 end] return "$res[parse-arguments code]" } open { append res [parse-command code] return "$res[parse-arguments code]" } close { return $res } } # start subcommands for math expressions # (+ 2 (* 3 4)) # ^ lispy::make-parser exprcmd { blank {} string - dvar { append res $string return "\[$res[parse-arguments code]\]" } var - cmd { if {[lispy::ismath $string]} { return "([join [parse-expr code] [lispy::math $string]])" } if {[lispy::ismathfun $string]} { return "[lispy::math $string]([join [parse-expr code] ", "])" } # put verbatim append res $string return "\[$res[parse-arguments code]\]" } tag { append res [string range $string 1 end] return "\[$res[parse-arguments code]\]" } open - expr { append res [parse-exprcmd code] return "\[$res[parse-arguments code]\]" } close { return $res } } lispy::make-parser block { blank { append res \n continue } number - string - var - cmd - tag - dvar { error "block expected" } open { append res \t[parse-command code]\n } close { return \{$res\} } } lispy::make-parser arguments { blank - number - string - cmd - dvar { # put verbatim append res $string } tag { append res [string range $string 1 end] } var { append res \$$string } open { append res "\[[parse-command code]\]" } expr { append res "{[parse-exprcmd code]}" } close { return $res } list { append res [parse-list code] } begin { append res [parse-block code] } } # math expressions lispy::make-parser expr { blank { #ignore } number - string - dvar { # put verbatim lappend res $string } tag - cmd { error "cannot handle tags in a mathematical expression" } var { lappend res \$$string } open - expr { lappend res [parse-exprcmd code] } close { return $res } list { lappend res [parse-list code] } begin { error "cannot have begin in mathematical expression" } } lispy::make-parser list { blank - number - string - cmd - var - dvar { # put verbatim append res $string } tag { append res [string range $string 1 end] } close { return \{$res\} } list - open { append res [parse-list code] } } proc lispy::parse code { lispy::parse-command code }