Advertisement

A simple and somewhat clunky Lisp interpreter in C#.

Started by October 26, 2009 06:53 PM
4 comments, last by RivieraKid 13 years, 8 months ago
As I was inspired by "Would you release bad code" thread, I've decided to release the source to this rather unstable and clunky Lisp interpreter I've been sitting on for a little while. I have less use for it than I thought I would (I was going to use it as a scripting engine), and I can't imagine anyone really using it for anything particularly productive, but it was a fun experiment so I'm going to let it loose. You guys can do whatever you like with it, including deride me for my poor coding ability and chew me out on whatever problems you have with it. I might still have a use for it after all, so any suggestions you have would be good ones. I know I'll get flak on documentation at the very least. I don't have much documentation on it, but at least there's a few comments. I always meant to go back and comment it better, but I'm not sure I'll have time now that the fall semester has set in. Some of the less commented stuff is self-explanatory, and some of it is less so. Some of it is an example of what you don't do and why you DO comment. The ConsCell.ToString() method is a perfect example of this. [grin] I'm sure I could have broken my source code up a little better. I'm sure there's still a few bugs lurking down in there. Here it is. edit: And for anyone who wants something to test it with, here is a simple Fahrenheit to Celcius converter program that does run in it:

(define (converter)
  (read "Enter 1 for celcius -> fahrenheit or 2 for fahrenheit -> celcius: " 'choice)
  (cond
    ((= choice 1)
      (progn
        (read "Enter the celcius value: " 'tc)
        (* (/ 9.0 5.0) (+ tc 32))))
    ((= choice 2)
      (progn
  	(read "Enter the fahrenheit value: " 'tf)
	(* (/ 5.0 9.0) (- tf 32))))
    (t (progn (write "Please enter 1 or 2." newline) (converter)))))
Hi Mate,

I was looking for some basic/simple interpreters and compilers for LISP online and came across this and found it quite handy. However, within your code I am trying to program a 'match' function that takes an s-expression as an argument and finds (matches) from a given set of sexpressions and executes the adjacent sexpressions to the matching one. So something like this...


(match (sexp)
((sexp) (sexp))
((sexp) (sexp))
((sexp) (sexp))
)

An example can be...

x= (1 2)
(match(x)
((1 2) ((car(cons 1 2))
((1 0) ((car(cons 1 1))
)

>(1 2 1)

The grammar for the match function is (not 100% correct I think)....

"(" "match" sexp <list-of-pairs> ")"
<list-of-pair>: "(" <test-match*>")"
<test-match>: "(" sexp sexp")"

Is there any tips you can give me relevant to your code on how I can implement this?
Advertisement
I'm assuming you've looked over the code, so that you'll understand which classes I'm talking about.

First of all, just to refresh your memory, the interpreter has a mechanism for adding functions and identifiers from the host program. Adding a new function to the system is quite simple to do - all that's necessary is defining the function somewhere, creating a delegate of the proper type, and then adding it to a RunContext via its SetIdentifier() method. There are two types of delegates that are salient here. The delegate type that most functions will use is simply called "Function." This is what most of the basic arithmetic and list manipulation functions are classed as. What's important here is that all parameters to a "Function" are evaluated before they reach the function. I found when I was implementing the interpreter's primitives that this was inadequate for certain primitives (like "quote", "eval" , "if", etc.). In any case, parameters are passed to the host functions through linked lists made from "ConsCells" - in other words, a Lisp list, which may or may not be what you need depending on context.

If you need to have a function that can take unevaluated s-forms by default (without having to quote them in the program itself, though you can do that too), then you write your function in the host program and add it to the RunContext through a "SpecialFunction" delegate. SpecialFunctions behave exactly like normal functions, except that their parameters are not evaluated prior to being passed in! So, if you want them to be evaluated, you'll have to evaluate them through the RunContext's "eval" method. You will probably have to build up s-forms yourself if you want to treat them as s-forms (lists), also through the RunContext. The method to use in this case is "BuildList", which takes a List<object> and transforms it into a linked list of ConsCells. I believe you can tell this method whether or not to evaluate individual terms in the list you're building, too, so you can build an entirely quoted list using this method.


So, for implementing the match function, I would probably use a "SpecialFunction" since you don't want the s-forms to be evaluated before you've had a chance to look at them (if at all). What I would do (assuming I'm understanding what you're after here) is store the list of terms you're looking for in some temporary variable, and then just iterate through the list looking at each match term and evaluating only when necessary. If it matches (not sure exactly how I would write that), then you just use RunContext.Eval to evaluate the expression that the term is associated with.

Hope that helped, I haven't looked at most of this code since I posted it in the first place. I had no idea anyone was using this for anything. :P
tl;dr

i am currently writing a javascript interpreter in F# as a learning experience using FSYacc and FSLex.

Its going well so far and suites the problem beautifully. Im interested in tackling lisp at some point in the future.

Why did you use C#? its very verbose.
1. I know C#. I didn't know F# (yet). In fact, I still haven't used F#, largely because of a lack of an IDE for it. This is primarily why I don't really use Common Lisp, D, Erlang, or Haskell very much, either.
2. At the time I wrote this (all the way back in 2008/09 - check out the post date on the OP!), F# wasn't really stable yet. VS2010 still had yet to come out, and F# was only (IIRC) available as a beta. I didn't want to depend on something that wasn't quite stable yet.
3. I originally wanted to use this in XNA framework games as a scripting system, and I wanted all my source files for the project to be accessible from the IDE. If I were going to use F#, I would have had to compile the interpreter as a separate assembly and reference it from within my project, and since I was using VC# express I wasn't certain that it would like F# source. Of course, when I released it I had to separate it all out into its own assembly and such, anyway, but that wasn't what I had really intended when I was originally writing the thing.

tl;dr It was less effort to just write it in C# than it would have been to write it in F#. The verbosity didn't bother me a bit - in fact, I tend to see excessive terseness as a bad thing, since it can make the code less clear. With this project, I was able to read and understand my own code a year and a half after I released it.
cool that all makes sense.

I agree with excessive terseness. One of the great benefits of F# is you can be terse or verbose. And thanks for teaching me a new word - "terse".

This topic is closed to new replies.

Advertisement