Initial state - basic lexer + parser + interpreter
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					_build
 | 
				
			||||||
							
								
								
									
										6
									
								
								bin/dune
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								bin/dune
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					(executable
 | 
				
			||||||
 | 
					 (name main)
 | 
				
			||||||
 | 
					 (public_name main)
 | 
				
			||||||
 | 
					 (libraries str lisp unix))
 | 
				
			||||||
 | 
					(include_subdirs unqualified)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								bin/main.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								bin/main.ml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					open Lisp.Ast;;
 | 
				
			||||||
 | 
					open Printf;;
 | 
				
			||||||
 | 
					open Lisp;;
 | 
				
			||||||
 | 
					open Eval;;
 | 
				
			||||||
 | 
					open Read;;
 | 
				
			||||||
 | 
					let rec repl c =
 | 
				
			||||||
 | 
					  let () = printf ">>> "; Out_channel.flush Out_channel.stdout; in
 | 
				
			||||||
 | 
					  match In_channel.input_line c with
 | 
				
			||||||
 | 
					  | None -> ()
 | 
				
			||||||
 | 
					  | Some l -> 
 | 
				
			||||||
 | 
					    let vals = (parse_str l) in
 | 
				
			||||||
 | 
					    (* dbg_print_all vals; *)
 | 
				
			||||||
 | 
					    dbg_print_all (eval_all vals);
 | 
				
			||||||
 | 
					    Out_channel.flush Out_channel.stdout;
 | 
				
			||||||
 | 
					    repl c;;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let _ = repl (In_channel.stdin)
 | 
				
			||||||
							
								
								
									
										2
									
								
								dune-project
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								dune-project
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					(lang dune 3.7)
 | 
				
			||||||
 | 
					(using menhir 2.1)
 | 
				
			||||||
							
								
								
									
										37
									
								
								lib/ast.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/ast.ml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					type lisp_val = 
 | 
				
			||||||
 | 
					  | LInt of int 
 | 
				
			||||||
 | 
					  | LDouble of float 
 | 
				
			||||||
 | 
					  | LCons of lisp_val * lisp_val
 | 
				
			||||||
 | 
					  | LNil
 | 
				
			||||||
 | 
					  | LSymbol of string
 | 
				
			||||||
 | 
					  | LString of string
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  (* a builtin function is expressed as a name and the ocaml function
 | 
				
			||||||
 | 
					  that performs the operation. The function should take a list of arguments.
 | 
				
			||||||
 | 
					  generally, builtin functions should handle their arguments directly,
 | 
				
			||||||
 | 
					  and eval forms in the environment as necessary. *)
 | 
				
			||||||
 | 
					  | LBuiltinFunction of string * (environment -> lisp_val -> lisp_val)
 | 
				
			||||||
 | 
					  (* a function is a name, a parameter list, and function body. *)
 | 
				
			||||||
 | 
					  | LFunction of string * lisp_val * lisp_val
 | 
				
			||||||
 | 
					  | LQuoted of lisp_val
 | 
				
			||||||
 | 
					and environment = (string, lisp_val) Hashtbl.t list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let rec dbg_print_one v =
 | 
				
			||||||
 | 
					  let pf = Printf.sprintf in
 | 
				
			||||||
 | 
					  match v with
 | 
				
			||||||
 | 
					  | LInt x ->  pf "<int: %d>" x
 | 
				
			||||||
 | 
					  | LSymbol s -> pf "<symbol: '%s'>" s
 | 
				
			||||||
 | 
					  | LString s -> pf "<string: '%s'>" s
 | 
				
			||||||
 | 
					  | LNil -> pf "()"
 | 
				
			||||||
 | 
					  | LCons (a, b) -> pf "(%s . %s)" (dbg_print_one a) (dbg_print_one b)
 | 
				
			||||||
 | 
					  | LDouble d -> pf "<double: %f>" d
 | 
				
			||||||
 | 
					  | LBuiltinFunction (name, _) -> pf "<builtin: %s>" name
 | 
				
			||||||
 | 
					  | LFunction (name, args, _) -> pf "<function: '%s' lambda-list: %s>" 
 | 
				
			||||||
 | 
					    name ((dbg_print_one args))
 | 
				
			||||||
 | 
					  | LQuoted v -> pf "<quote: %s>" (dbg_print_one v)
 | 
				
			||||||
 | 
					  (*| _ -> "<Something else>"*)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let dbg_print_all vs =
 | 
				
			||||||
 | 
					  let pr v = Printf.printf "%s\n" (dbg_print_one v) in
 | 
				
			||||||
 | 
					  List.iter pr vs
 | 
				
			||||||
							
								
								
									
										9
									
								
								lib/dune
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								lib/dune
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					(library
 | 
				
			||||||
 | 
					 (name lisp)
 | 
				
			||||||
 | 
					 ;(modules ast read lexer parser)
 | 
				
			||||||
 | 
					 )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(include_subdirs unqualified)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(menhir (modules parser))
 | 
				
			||||||
 | 
					(ocamllex lexer)
 | 
				
			||||||
							
								
								
									
										100
									
								
								lib/eval.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								lib/eval.ml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
				
			|||||||
 | 
					open Ast;;
 | 
				
			||||||
 | 
					open InterpreterStdlib;;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let default_env: environment = [Hashtbl.create 1024];;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let env_add env s v =
 | 
				
			||||||
 | 
					  match env with
 | 
				
			||||||
 | 
					  | [] -> ()
 | 
				
			||||||
 | 
					  | e1 :: _ ->  Hashtbl.add e1 s v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let env_new_lexical env = 
 | 
				
			||||||
 | 
					  let h = Hashtbl.create 16 in
 | 
				
			||||||
 | 
					  h :: env
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let add_builtin s f =
 | 
				
			||||||
 | 
					  env_add default_env s (LBuiltinFunction (s, f))
 | 
				
			||||||
 | 
					let () = add_builtin "+" iadd
 | 
				
			||||||
 | 
					let () = add_builtin "-" isub
 | 
				
			||||||
 | 
					let () = add_builtin "car" car
 | 
				
			||||||
 | 
					let () = add_builtin "cdr" cdr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let make_env () = [Hashtbl.copy (List.hd default_env)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(* the type annotations are unnecessary, but help constrain us from a
 | 
				
			||||||
 | 
					potentially more general function here *)
 | 
				
			||||||
 | 
					let rec eval_sym (env: environment) (s: string) =
 | 
				
			||||||
 | 
					  match env with
 | 
				
			||||||
 | 
					  | [] -> raise (Invalid_argument (Printf.sprintf "eval_sym: symbol %s has no value in current scope" s))
 | 
				
			||||||
 | 
					  | e :: rest ->
 | 
				
			||||||
 | 
					    match Hashtbl.find_opt e s with
 | 
				
			||||||
 | 
					    | None -> eval_sym rest s
 | 
				
			||||||
 | 
					    | Some v -> v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let rec eval_one env v = 
 | 
				
			||||||
 | 
					  match v with
 | 
				
			||||||
 | 
					  | LInt x -> LInt x
 | 
				
			||||||
 | 
					  | LDouble x -> LDouble x
 | 
				
			||||||
 | 
					  | LString s -> LString s
 | 
				
			||||||
 | 
					  | LSymbol s -> eval_sym env s
 | 
				
			||||||
 | 
					  | LNil -> LNil
 | 
				
			||||||
 | 
					  | LCons (func, args) -> eval_call env func args
 | 
				
			||||||
 | 
					  | LBuiltinFunction (n, f) -> LBuiltinFunction (n, f)
 | 
				
			||||||
 | 
					  | LFunction (n, l, f) -> LFunction (n, l, f)
 | 
				
			||||||
 | 
					  | LQuoted v -> v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(* Evaluate a list of values, without evaluating the resulting
 | 
				
			||||||
 | 
					function or macro call. Since macros and functions inherently
 | 
				
			||||||
 | 
					look similar, they share a lot of code, which is extracted here *)
 | 
				
			||||||
 | 
					and eval_list env l =
 | 
				
			||||||
 | 
					  match l with
 | 
				
			||||||
 | 
					  | LNil -> LNil
 | 
				
			||||||
 | 
					  | LCons (a, b) -> LCons (eval_one env a, eval_list env b)
 | 
				
			||||||
 | 
					  | _ -> raise (Invalid_argument "eval_list: cannot process non-list")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					and eval_body env body =
 | 
				
			||||||
 | 
					  match body with
 | 
				
			||||||
 | 
					  | LNil -> LNil
 | 
				
			||||||
 | 
					  | LCons (form, LNil) -> eval_one env form
 | 
				
			||||||
 | 
					  | LCons (form, next) -> 
 | 
				
			||||||
 | 
					    ignore (eval_one env form); eval_body env next
 | 
				
			||||||
 | 
					  | _ -> LNil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					and eval_apply env func args =
 | 
				
			||||||
 | 
					  let lexical_env = env_new_lexical env in
 | 
				
			||||||
 | 
					  let rec bind_one s a =
 | 
				
			||||||
 | 
					    match a with
 | 
				
			||||||
 | 
					    | LNil -> raise (Invalid_argument "not enough arguments supplied to function")
 | 
				
			||||||
 | 
					    | LCons (value, resta) ->
 | 
				
			||||||
 | 
					      env_add lexical_env s value; resta
 | 
				
			||||||
 | 
					    | _ -> raise (Invalid_argument "invalid argument list")
 | 
				
			||||||
 | 
					  and bind_args l a =
 | 
				
			||||||
 | 
					    match l with
 | 
				
			||||||
 | 
					    | LNil -> (match a with
 | 
				
			||||||
 | 
					      | LNil -> ()
 | 
				
			||||||
 | 
					      | _ -> raise (Invalid_argument "too many arguments supplied to function"))
 | 
				
			||||||
 | 
					    | LCons (LSymbol sym, LSymbol restl)->
 | 
				
			||||||
 | 
					      env_add lexical_env restl (bind_one sym a)
 | 
				
			||||||
 | 
					    | LCons (LSymbol sym, restl) ->
 | 
				
			||||||
 | 
					      bind_args restl (bind_one sym a)
 | 
				
			||||||
 | 
					    | _ -> raise (Invalid_argument "Failure while binding arguments")
 | 
				
			||||||
 | 
					  in match func with
 | 
				
			||||||
 | 
					  | LFunction (_, l, b) ->
 | 
				
			||||||
 | 
					    bind_args l args;
 | 
				
			||||||
 | 
					    eval_body lexical_env b
 | 
				
			||||||
 | 
					  | _ -> LNil
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					and eval_call env func args =
 | 
				
			||||||
 | 
					  match eval_one env func with
 | 
				
			||||||
 | 
					  | LBuiltinFunction (_, f) -> f env (eval_list env args)
 | 
				
			||||||
 | 
					  | LFunction (n, l, b) -> eval_apply env (LFunction (n, l, b)) (eval_list env args)
 | 
				
			||||||
 | 
					  | v -> raise (Invalid_argument 
 | 
				
			||||||
 | 
					        (Printf.sprintf "eval_apply: cannot call non-function object %s" (dbg_print_one v)));;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let eval_all vs =
 | 
				
			||||||
 | 
					  let env = make_env () in
 | 
				
			||||||
 | 
					  let ev v = eval_one env v in
 | 
				
			||||||
 | 
					  List.map ev vs
 | 
				
			||||||
							
								
								
									
										46
									
								
								lib/interpreterStdlib.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/interpreterStdlib.ml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					open Ast;;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let iadd _ vs : lisp_val = 
 | 
				
			||||||
 | 
					  let rec auxi vs accum = 
 | 
				
			||||||
 | 
					    match vs with
 | 
				
			||||||
 | 
					    | LCons (LInt a, b) -> (auxi b (accum + a))
 | 
				
			||||||
 | 
					    | LCons (LDouble a, b) -> (auxf b ((float_of_int accum) +. a))
 | 
				
			||||||
 | 
					    | _ -> LInt accum
 | 
				
			||||||
 | 
					  and auxf vs accum =
 | 
				
			||||||
 | 
					    match vs with
 | 
				
			||||||
 | 
					    | LCons (LInt a, b) -> (auxf b (accum +. (float_of_int a)))
 | 
				
			||||||
 | 
					    | LCons (LDouble a, b) -> (auxf b (accum +. a))
 | 
				
			||||||
 | 
					    | _ -> LDouble accum
 | 
				
			||||||
 | 
					  in (auxi vs 0);;
 | 
				
			||||||
 | 
					let isub _ vs = 
 | 
				
			||||||
 | 
					  let rec auxi vs accum = 
 | 
				
			||||||
 | 
					    match vs with
 | 
				
			||||||
 | 
					    | LNil -> LInt accum
 | 
				
			||||||
 | 
					    | LCons (LInt a, b) -> auxi b (accum - a)
 | 
				
			||||||
 | 
					    | LCons (LDouble a, b) -> auxf b ((float_of_int accum) -. a)
 | 
				
			||||||
 | 
					    | _ -> raise (Invalid_argument "-: invalid argument to subtraction operator")
 | 
				
			||||||
 | 
					  and auxf vs accum = 
 | 
				
			||||||
 | 
					    match vs with
 | 
				
			||||||
 | 
					    | LNil -> LDouble accum
 | 
				
			||||||
 | 
					    | LCons (LInt a, b) -> auxf b (accum -. (float_of_int a))
 | 
				
			||||||
 | 
					    | LCons (LDouble a, b) -> auxf b (accum -. a)
 | 
				
			||||||
 | 
					    | _ -> raise (Invalid_argument "-: invalid argument to subtraction operator")
 | 
				
			||||||
 | 
					  in
 | 
				
			||||||
 | 
					  match vs with
 | 
				
			||||||
 | 
					  | LCons (LInt a, LNil) -> LInt (-a)
 | 
				
			||||||
 | 
					  | LCons (LInt a, b) -> auxi b a
 | 
				
			||||||
 | 
					  | LCons (LDouble a, LNil) -> LDouble (-. a)
 | 
				
			||||||
 | 
					  | LCons (LDouble a, b) -> auxf b a
 | 
				
			||||||
 | 
					  | _ -> auxi vs 0;;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let car _ vs =
 | 
				
			||||||
 | 
					  match vs with
 | 
				
			||||||
 | 
					  | LCons (LCons (a, _), LNil) -> a
 | 
				
			||||||
 | 
					  | _ -> raise (Invalid_argument "car: invalid argument")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let cdr _ vs =
 | 
				
			||||||
 | 
					  match vs with
 | 
				
			||||||
 | 
					  | LCons (LCons (_, b), LNil) -> b
 | 
				
			||||||
 | 
					  | _ -> raise (Invalid_argument "cdr: invalid argument")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										36
									
								
								lib/lexer.mll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								lib/lexer.mll
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					open Lexing
 | 
				
			||||||
 | 
					open Parser
 | 
				
			||||||
 | 
					exception SyntaxError of string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let strip_quotes s = String.sub s 1 (String.length s - 2);;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let digit = ['0'-'9']
 | 
				
			||||||
 | 
					let number_sign = '-' | '+'
 | 
				
			||||||
 | 
					let int = number_sign? digit+
 | 
				
			||||||
 | 
					let double = digit* '.' digit+ | digit+ '.' digit*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let white = [' ' '\t']+
 | 
				
			||||||
 | 
					let newline = '\r' | '\n' | "\r\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let sym_char = ['a'-'z' 'A'-'Z' '!' '\\' '+' '-' '*' '/' '_' '?']
 | 
				
			||||||
 | 
					let sym = sym_char sym_char*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let str = '"' [^'"']* '"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rule read =
 | 
				
			||||||
 | 
					    parse
 | 
				
			||||||
 | 
					    | white { read lexbuf }
 | 
				
			||||||
 | 
					    | newline { new_line lexbuf; read lexbuf}
 | 
				
			||||||
 | 
					    | int { INT (int_of_string (Lexing.lexeme lexbuf))}
 | 
				
			||||||
 | 
					    | double { DOUBLE (float_of_string (Lexing.lexeme lexbuf))}
 | 
				
			||||||
 | 
					    | sym { SYM (Lexing.lexeme lexbuf)}
 | 
				
			||||||
 | 
					    | str { STR (strip_quotes (Lexing.lexeme lexbuf))}
 | 
				
			||||||
 | 
					    | '(' { LPAREN }
 | 
				
			||||||
 | 
					    | ')' { RPAREN }
 | 
				
			||||||
 | 
					    | '\'' { QUOTE }
 | 
				
			||||||
 | 
					    | _ { raise (SyntaxError ("Unexpected char: " ^ Lexing.lexeme lexbuf))}
 | 
				
			||||||
 | 
					    | eof { EOF }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								lib/parser.mly
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/parser.mly
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					%{
 | 
				
			||||||
 | 
					    open Ast
 | 
				
			||||||
 | 
					%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%token <int> INT
 | 
				
			||||||
 | 
					%token <float> DOUBLE
 | 
				
			||||||
 | 
					%token <string> SYM
 | 
				
			||||||
 | 
					%token <string> STR
 | 
				
			||||||
 | 
					%token LPAREN
 | 
				
			||||||
 | 
					%token RPAREN
 | 
				
			||||||
 | 
					%token QUOTE
 | 
				
			||||||
 | 
					%token EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%start <Ast.lisp_val option> prog
 | 
				
			||||||
 | 
					%%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					prog:
 | 
				
			||||||
 | 
					    | EOF { None }
 | 
				
			||||||
 | 
					    | e = expr { Some e }
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					expr:
 | 
				
			||||||
 | 
					    | i = INT { LInt i }
 | 
				
			||||||
 | 
					    | d = DOUBLE {LDouble d}
 | 
				
			||||||
 | 
					    | s = SYM { LSymbol s }
 | 
				
			||||||
 | 
					    | s = STR { LString (String.uppercase_ascii s) }
 | 
				
			||||||
 | 
					    | LPAREN; l = lisp_list_rest { l }
 | 
				
			||||||
 | 
					    | QUOTE; e = expr { LQuoted e}
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lisp_list_rest:
 | 
				
			||||||
 | 
					    | RPAREN { LNil }
 | 
				
			||||||
 | 
					    | e = expr; lr = lisp_list_rest { LCons (e, lr) }
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
							
								
								
									
										13
									
								
								lib/read.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/read.ml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					let parse_one lb = Parser.prog (Lexer.read) lb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let parse lb = 
 | 
				
			||||||
 | 
					  let rec helper () =
 | 
				
			||||||
 | 
					    match parse_one lb with
 | 
				
			||||||
 | 
					    | None -> []
 | 
				
			||||||
 | 
					    | Some (t) -> t :: helper ()
 | 
				
			||||||
 | 
					  in
 | 
				
			||||||
 | 
					  helper ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let parse_str s =
 | 
				
			||||||
 | 
					  parse (Lexing.from_string s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in New Issue
	
	Block a user