(***********************************************************)
(* LISP Interpreter *)
(* *)
(* Your Name Here *)
(* Reference Skeleton, CSCI 350 *)
(* *)
(***********************************************************)
open Printf;;
open Stream;;
exception ParseError of string;;
exception ParseFailure of string;;
exception ParseOK;;
exception ParseEOF;;
exception EvalError of string;;
exception UnboundVar;;
exception ParameterMismatch;;
(***********************************************************)
(* type declarations *)
(***********************************************************)
type sign =
Plus
| Minus
;;
type atom =
T
| NIL
| Int of int
| Ident of string
;;
type token =
Lparen
| Rparen
| Dot
| Sign of sign
| Atom of atom
;;
type sexp =
AtomExp of atom
| Sexp of sexp * sexp
;;
(***********************************************************)
(* globals *)
(***********************************************************)
let lineno = ref 1;;
let top_level = ref true;;
let dlist = ref (AtomExp(NIL));;
(***********************************************************)
(* printing functions *)
(***********************************************************)
(* function: print_tokens - prints out a token stream *)
let rec print_tokens ts =
match ts with parser
[< 'Lparen; _ >] -> printf("Lparen "); print_tokens ts
| [< 'Rparen; _ >] -> printf("Rparen "); print_tokens ts
| [< 'Dot; _ >] -> printf("Dot "); print_tokens ts
| [< 'Sign s; _ >] -> (match s with
Plus -> printf("Plus "); print_tokens ts
| Minus -> printf("Minus "); print_tokens ts)
| [< 'Atom a; _ >] -> (match a with
T -> printf("Atom(T) "); print_tokens ts
| NIL -> printf("Atom(NIL) "); print_tokens ts
| Int n -> printf("Atom(Int(%d)) ") n; print_tokens ts
| Ident s -> printf("Atom(Ident(%s)) ") s; print_tokens ts)
| [< >] -> printf("\n")
;;
(* function: string_of_op - converts an operator token to a string *)
let string_of_op o =
match o with
Plus -> "+"
| Minus -> "-"
;;
(* function: is_list - predicate function returning true if s-expression is a list *)
let rec is_list s =
match s with
Sexp(h, AtomExp(NIL)) -> true
| Sexp(h, t) -> is_list t
| _ -> false
;;
(* function: string_of_atom - converts a primitive atom to a string *)
let string_of_atom a =
match a with
T -> "t"
| NIL -> "nil"
| Int(i) -> string_of_int i
| Ident(i) -> i
;;
(* function: string_of_token - converts a lexer token to a string *)
let string_of_token t =
match t with
Lparen -> "("
| Rparen -> ")"
| Dot -> "."
| Sign(o) -> string_of_op o
| Atom(a) -> string_of_atom a
;;
(* function: print_list - prints an s-expression in list format *)
let rec print_list s =
match s with
AtomExp(NIL) -> ()
| AtomExp(a) -> printf("%s") (string_of_atom a)
| Sexp(h,AtomExp(NIL)) -> print_sexp h
| Sexp(h,t) ->
(print_sexp h;
printf(" ");
print_list t)
(* function: print_sexp - prints an s-expression in either dotted or list format *)
and print_sexp s =
if (is_list s) then
(printf("(");
print_list s;
printf(")");)
else
(match s with
AtomExp(a) -> printf("%s") (string_of_atom a)
| Sexp(h,t) ->
(printf("(");
print_sexp h;
printf(" . ");
print_sexp t;
printf(")")))
;;
(***********************************************************)
(* lexer implementation *)
(***********************************************************)
let rec spaces s =
match s with parser
[< '' '|'\t'; _ >] -> spaces s
| [< ''\n' ; _ >] -> incr lineno; spaces s
| [< >] -> ()
;;
let rec lexid str s =
match s with parser
[< ''a'..'z'|'A'..'Z'|'0'..'9' as c ; _ >] -> lexid (str ^ (Char.escaped c)) s
| [< >] -> str
;;
let rec lexint v s =
match s with parser
[< ''0'..'9' as n; _ >] -> lexint ((10*v)+(int_of_string (Char.escaped n))) s
| [< >] -> v
;;
let rec lexer s =
match s with parser
[< ''(' ; _ >] -> [< 'Lparen ; lexer s >]
| [< '')' ; _ >] -> [< 'Rparen ; lexer s >]
| [< ''.' ; _ >] -> [< 'Dot ; lexer s >]
| [< ''-' ; _ >] -> [< 'Sign(Minus) ; lexer s >]
| [< ''+' ; _ >] -> [< 'Sign(Plus) ; lexer s >]
| [< ''a'..'z'|'A'..'Z' as c; _ >] -> [< 'Atom(Ident(lexid (Char.escaped c) s)); lexer s >]
| [< ''0'..'9' as n; _ >] -> [< 'Atom(Int(lexint (int_of_string (Char.escaped n)) s)); lexer s >]
| [< '' '|'\t' ; _ >] -> spaces s; [< lexer s >]
| [< ''\n' ; _ >] -> incr lineno; spaces s; [< lexer s >]
| [< 'c >] -> printf("ERROR: Illegal character on line %d: %c\n") !lineno c; lexer s
;;
(***********************************************************)
(* parser implementation *)
(***********************************************************)
(* function: eat - consumes next value in token stream *)
let eat ts = try next ts; () with Stream.Failure -> ();;
(* function: check_sign - both validates and combines sign and integer token pairs *)
let check_sign s ts =
let lookahead = peek ts in
match lookahead with
Some t ->
(match t with
Atom a ->
(match a with
Int a ->
if (s=Minus) then
(eat ts; AtomExp(Int(-a)))
else
(eat ts; AtomExp(Int(a)))
| _ -> raise (ParseError("+/- sign may only be used with integer literals")))
| _ -> raise (ParseError("+/- sign may only be used with integer literals")))
| None -> raise (ParseError("EOF found before complete parsing of integer literal"))
;;
(* function: check_atom - converts identifiers to internal primitives, nested defun check *)
let check_atom a =
match a with
Ident i -> (
match i with
"T" | "t" -> AtomExp(T)
| "NIL" | "nil" -> AtomExp(NIL)
| "DEFUN" | "defun" ->
if !top_level = true then
AtomExp(a)
else
raise (ParseError("DEFUN used outside of top-level"))
| _ -> AtomExp(a))
| _ -> AtomExp(a)
;;
(* function: parse_sexp - top-level parser: takes stream of tokens, returns sexp-tree *)
(* S ::= E *)
let rec parse_sexp ts =
try
top_level := true;
let lookahead = (peek ts) in
match lookahead with
Some cur -> parse_exp ts
| None -> raise ParseOK
with
Stream.Failure -> raise ParseOK
| ParseFailure msg -> raise (ParseError(msg))
| ParseEOF -> raise (ParseError("found EOF before complete s-expression parsing"))
(* E ::= atom | '(' X *)
and parse_exp ts =
try
let lookahead = (peek ts) in
match lookahead with
Some cur ->
(match cur with
Lparen -> eat ts; parse_x ts
| Sign(s) -> eat ts; check_sign s ts
| Atom(a) -> eat ts; check_atom a
| _ -> raise (ParseFailure("parser expected '(' or an atom expression")))
| None -> raise (ParseFailure("parse ended expecting '(' or an atom expression"))
with
Stream.Failure -> raise (ParseFailure("found EOF before complete s-expression parsing"))
(* X ::= E Y | ')' *)
and parse_x ts =
try
let lookahead = (peek ts) in
match lookahead with
Some cur ->
(match cur with
Rparen -> eat ts; AtomExp(NIL) (* handle '()' *)
| _ -> let e = parse_exp ts in
let y = parse_y ts in
Sexp(e,y))
| None -> raise (ParseFailure("parser ended expecting ')' or an s-expression"))
with
Stream.Failure -> raise (ParseFailure("found EOF before complete s-expression parsing"))
(* Y ::= '.' E ')' | R ')' *)
and parse_y ts =
try
let lookahead = (peek ts) in
match lookahead with
Some cur ->
(match cur with
Dot ->
eat ts;
let e = parse_exp ts in
parse_rparen ts; e
| _ -> let r = parse_r ts in
parse_rparen ts; r)
| None -> raise (ParseFailure("parser ended expecting '.' or an s-expression"))
with
Stream.Failure -> raise (ParseFailure("found EOF before complete s-expression parsing"))
(* R ::= E R | empty *)
and parse_r ts =
let lookahead = (peek ts) in
match lookahead with
Some cur ->
(match cur with
Lparen|Sign(_)|Atom(_) ->
let e = parse_exp ts in
let r = parse_r ts in
Sexp(e,r)
| _ -> AtomExp(NIL))
| None -> raise (ParseFailure("parser ended expecting ')' or an s-expression"))
(* convenience production for right parens *)
and parse_rparen ts =
try
let lookahead = (peek ts) in
match lookahead with
Some cur ->
(match cur with
Rparen -> eat ts;
| _ -> raise (ParseFailure("expected ')'")))
| None -> raise (ParseFailure("parser ended expecting ')'"))
with
Stream.Failure -> raise (ParseFailure("found EOF before complete s-expression parsing"))
;;
(*****************************************)
(* interpretation functions *)
(*****************************************)
(* function: bound - checks that referenced variables are bound in a-list *)
(* returns true if found, false otherwise x is a string *)
let rec bound x alist =
match alist with
Sexp(AtomExp(Ident(group)),threst) ->
if x == group then true else bound x threst
|_ -> false
;;
(* function: getval - returns the value of a variable from the a-list & d-list name is a string*)
let rec getval name alist =
match alist with
Sexp(Sexp(AtomExp(Ident(group)), var),threst) ->
if (name == group) then var else (getval name threst)
|_-> raise(EvalError("Dick jones says I know."))
;;
(* function: eval_defun - checks defun usage and adds function def to the global d-list *)
(* returns unit *)
let eval_defun s a d =
(* modifies !d / d:= Sexp() *)
match s with
Sexp(AtomExp(Ident(name)),Sexp(AtomExp(Ident(formals)),Sexp(AtomExp(Ident(body)),AtomExp(NIL)))) ->
Sexp(Sexp(AtomExp(Ident(name)),Sexp(AtomExp(Ident(formals)),AtomExp(Ident(body)))),!d)
|_ -> AtomExp(NIL)
;;
(* function: check_formal - checks function parameters from matching T or NIL *)
let check_formal x =
match x with
AtomExp(T) -> raise (EvalError("Formal parameters may not be 'T'"))
| AtomExp(NIL) -> raise (EvalError("Formal parameters may not be 'NIL'"))
| _ -> ()
;;
(* function: addpairs - checks function parameters and binds formals to actuals *)
(* returns an s-expression *)
let rec addpairs xlist ylist z =
(* calls check_formal *)
match xlist with
Sexp(Sexp(AtomExp(xy), t), Sexp(AtomExp(yl), v)) -> addpairs t v (Sexp(z,(Sexp(AtomExp(xy), AtomExp(yl)))))
|_-> z
;;
(* function: eval - top-level s-expression evaluation loop *)
(* returns an s-expression *)
let rec eval exp a d =
match exp with
AtomExp(T) -> AtomExp(T)
|AtomExp(NIL) -> AtomExp(NIL)
|AtomExp(Int(_)) -> exp
|AtomExp(Ident(s)) -> if ((bound s a) ==true) then (getval s a) else raise (EvalError ("You done goof'd"))
|_-> (match exp with
Sexp(AtomExp(Ident("QUOTE")), Sexp(h,r)) -> if (r <> AtomExp(NIL))
then (raise (EvalError("Honey badger says you can't quote"))) else h
|Sexp(AtomExp(Ident("COND")), r) -> evcon r a d
|Sexp(AtomExp(Ident("DEFUN")), r) -> eval_defun r a d
|Sexp(h, t) -> apply h (evlist t a d) a d
|_ -> raise(EvalError("Hide your kids, Hide your wives")))
(* function: evcon - evaluates a COND statement *)
(* returns an s-expression *)
and evcon x a d =
match x with
AtomExp(NIL) -> raise(EvalError("Nyan cat does not approve"))
|Sexp(Sexp(h,j), Sexp(q, t))-> Sexp((eval h a d), (eval q a d))
|Sexp(h, t) -> evcon t a d
|_-> raise(EvalError("Y u No Cons Right?"))
(* function: evlist - evaluates a list of expressions and returns a list of results *)
(* returns an s-expression *)
and evlist sexp a d =
match sexp with
Sexp(h,t) -> Sexp((eval h a d),(evlist t a d))
|_ -> raise(EvalError("No can do mon"))
(* function: apply - performs function application, handles built-ins *)
(* returns an s-expression
Takes a car of an sexp, an sexp, a-list, d-list *)
and apply f x a d =
match f with
Sexp(AtomExp(Ident("CAR")),_) ->
match x with
Sexp(Sexp(fh,ft),Sexp(sh,st)) -> fh
|_-> raise(EvalError("Stars Wars Kid can code better than this"))
|Sexp(AtomExp(Ident("CDR")),_) ->
match x with
Sexp(Sexp(fh,ft),Sexp(sh,st)) -> ft
|_-> raise(EvalError("Professor Oak says your not a girl or boy"))
|Sexp(AtomExp(Ident("CONS")),_) ->
match x with
Sexp(car,Sexp(sh,st)) -> Sexp(car,sh)
|_-> raise(EvalError("Change major while you still can"))
|Sexp(AtomExp(Ident("ATOM")),_) ->
match x with
Sexp(car, cdr) -> car
|_-> raise(EvalError("Ash Ketchum GO STD'S"))
|Sexp(AtomExp(Ident("EQ")), _) ->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) ->
if first <> sec then AtomExp(NIL) else AtomExp(T)
|_-> raise(EvalError("Epic Baby FAIL"))
|Sexp(AtomExp(Ident("INT")),_) ->
match x with
Sexp(AtomExp(Ident(first)),AtomExp(NIL)) -> AtomExp(NIL)
|Sexp(AtomExp(Int(first)),AtomExp(NIL)) -> AtomExp(T)
|_-> raise(EvalError("Awkward Penguin 0_o"))
|Sexp(AtomExp(Ident("NULL")),_) ->
match x with
Sexp(AtomExp(NIL), AtomExp(NIL)) -> AtomExp(T)
|Sexp(AtomExp(_),AtomExp(NIL)) -> AtomExp(NIL)
|_ -> raise(EvalError("You can't nill, null, nor read instructions son"))
|Sexp(AtomExp(Ident("PLUS")),_)->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> AtomExp(Int(first+sec))
|_-> raise(EvalError("Trololol"))
|Sexp(AtomExp(Ident("MINUS")),_)->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> AtomExp(Int(first-sec))
|_-> raise(EvalError("Tech Something Mother FAIL"))
|Sexp(AtomExp(Ident("TIMES")),_)->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> AtomExp(Int(first*sec))
|_-> raise(EvalError("Music Obvious Eighth Grader says B flat"))
|Sexp(AtomExp(Ident("QUOTIENT")),_)->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> if sec == 0 then raise(EvalError("YOU BLEW UP THE WORLD"))
else AtomExp(Int(first/sec))
|_-> raise(EvalError("Pickup Line Panda says Hi"))
|Sexp(AtomExp(Ident("REMAINDER")),_) ->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> AtomExp(Int((first mod sec)))
|_-> raise(EvalError("Forever Alone"))
|Sexp(AtomExp(Ident("LESS")),_) ->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> if first < sec then
AtomExp(T) else AtomExp(NIL)
|_-> raise(EvalError("Lesser Soul Crystal Needed"))
|Sexp(AtomExp(Ident("GREATER")),_) ->
match x with
Sexp(AtomExp(Int(first)), AtomExp(Int(sec))) -> if first > sec then
AtomExp(T) else AtomExp(NIL)
|_-> raise(EvalError("Nothings is Greater than Chuck Norris"))
|_ -> match f with
Sexp(AtomExp(Ident(fname)), _) -> if ((bound fname !d) == true)
then
let
stuffz = (getval fname !d)
in
match stuffz with
Sexp(h,t) -> (eval t (addpairs h x a) d)
|_-> raise(EvalError("Hey stop that!"))
else raise(EvalError("Nope! Chuck Testa."))
|_-> raise(EvalError("Don't look at me"))
;;
(*****************************************)
(* helper routines *)
(*****************************************)
(* function: get_sexp - wrapper function around parser to catch stream failures *)
let get_sexp s =
try
let sexplist = parse_sexp s in
sexplist
with
Stream.Failure -> AtomExp(NIL)
;;
(* function: next_sexp - recursive function which tries to evaluate as much as possible *)
let rec next_sexp ts =
try
let e = (eval (get_sexp ts) (AtomExp(NIL)) dlist) in
print_sexp e; printf("\n"); next_sexp ts
with
ParseError e -> printf("Parse Error: %s\n") e
| EvalError e -> printf("Evaluation Error: %s\n") e; next_sexp ts
| ParseOK -> exit 0
;;
(*****************************************)
(* main *)
(*****************************************)
let ts = lexer (Stream.of_channel stdin) in
try
(* read/evaluate/print loop *)
(print_sexp(eval (get_sexp ts) (AtomExp(NIL)) dlist);
printf("\n");
next_sexp ts)
with
ParseError e -> printf("Parse Error: %s\n") e
| EvalError e -> printf("Evaluation Error: %s\n") e; next_sexp ts
| ParseOK -> exit 0
;;