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} 0Blocks 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
}