ref: 120a0cb0fd9df6a5da5d0ba480d6eb9b8b6d66a8
dir: /m9.ml/
(*
Martian Scheme
Copyright 2020, McKay Marston
This is a project for me to
1) Get more familiar with OCaml.
2) Try to provide a natively supported r7rs-small scheme for Plan9.
It is heavily inspired by s9fes (http://www.t3x.org/s9fes), and the
make a lisp project (https://github.com/kanaka/mal - thanks
https://github.com/chouser for the fantastic implementation!)
*)
module T = Types.Types
let repl_env = Env.make (Some Core.base)
let synext_literals = T.String "syntax literals"
let synext_transformers = T.String "syntax transformers"
let rec quasiquote ast =
match ast with
| T.List { T.value = [ T.Symbol { T.value = "unquote" }; ast ] } -> ast
| T.Vector { T.value = [ T.Symbol { T.value = "unquote" }; ast ] } -> ast
| T.List
{ T.value =
T.List { T.value = [ T.Symbol { T.value = "unquote-splicing" }; head ] } :: tail
}
| T.Vector
{ T.value =
T.List { T.value = [ T.Symbol { T.value = "unquote-splicing" }; head ] } :: tail
} -> Types.list [ Types.symbol "concat"; head; quasiquote (Types.list tail) ]
| T.List { T.value = head :: tail } | T.Vector { T.value = head :: tail } ->
Types.list [ Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ]
| ast -> Types.list [ Types.symbol "quote"; ast ]
;;
let is_macro_call ast env =
match ast with
| T.List { T.value = s :: args } ->
(match
try Env.get env s with
| _ -> T.Nil
with
| T.Proc { T.meta = T.Map { T.value = meta } } ->
Types.M9map.mem Core.kw_macro meta
&& Types.to_bool (Types.M9map.find Core.kw_macro meta)
| T.List { T.value = macro } ->
(match macro with
| kw :: _ -> kw = Types.symbol "syntax-rules"
| _ -> false)
| _ -> false)
| _ -> false
;;
let eval_macro sym args macro env =
(match macro with
| _ :: literals :: groups ->
let sgroups = Str.global_replace
(Str.regexp "(_")
("(" ^ Printer.print sym true)
(Printer.dump groups) in
print_endline ("BLARGH: " ^ sgroups);
print_endline ("TOKENIZED: " ^ String.concat " " (Reader.tokenize ("(" ^ sgroups ^ ")")));
let rec handle_groups groups =
(match groups with
| hd :: tl -> print_endline (" HD: " ^ Printer.print hd true ^ " tl: " ^ Printer.dump tl); handle_groups tl
| _ -> print_endline "<list end>") in
handle_groups groups;
let list_reader = Reader.read_list ")" {list_form = []; tokens = (Reader.tokenize (sgroups ^ ")")) } in
let slist = Types.list list_reader.list_form in
print_endline ("BLAAAARGH: " ^ Printer.print slist true);
| _ -> ());
let smacro =
Str.global_replace
(Str.regexp "(_")
("(" ^ Printer.print sym true)
(Printer.dump macro)
in
print_endline
("eval_macro: sym:"
^ Printer.print sym true
^ " args:"
^ Printer.dump args
^ " straight macro: "
^ Printer.dump macro);
print_endline (" subbed macro:" ^ smacro);
(* let sub_env = Env.make (Some env) in *)
match Reader.read smacro with
| T.List { T.value = transformer } ->
print_endline (" TRANSFORMER: " ^ Printer.dump transformer)
| _ -> ()
;;
let rec macroexpand ast env =
if is_macro_call ast env
then (
print_endline (" YES!: " ^ Printer.print ast true);
match ast with
| T.List { T.value = s :: args } ->
print_endline ("macroexpand macro symbol: " ^ Printer.print s true ^ " args: " ^ Printer.dump args);
(match
try Env.get env s with
| _ -> T.Nil
with
| T.Proc { T.value = f } -> macroexpand (f args) env
| T.List { T.value = macro } ->
eval_macro s args macro env;
ast
| _ -> ast)
| _ -> ast)
else ast
;;
let rec eval_ast ast env =
match ast with
| T.Symbol s -> Env.get env ast
| T.List { T.value = xs; T.meta } ->
T.List { T.value = List.map (fun x -> eval x env) xs; T.meta }
| T.Vector { T.value = xs; T.meta } ->
T.Vector { T.value = List.map (fun x -> eval x env) xs; T.meta }
| _ -> ast
and eval ast env =
match macroexpand ast env with
| T.List { T.value = [] } -> ast
(* Can this be replaced with a define-syntax thing? *)
| T.List
{ T.value = [ T.Symbol { T.value = "define" }; T.List { T.value = arg_list }; body ]
} ->
let sym = List.hd arg_list in
let rest = List.tl arg_list in
let func =
eval
(Reader.read
("(lambda ("
^ String.concat " " (List.map (fun x -> Printer.print x false) rest)
^ ") "
^ Printer.print body true
^ ")"))
env
in
Env.set env sym func;
func
| T.List { T.value = [ T.Symbol { T.value = "define" }; key; expr ] } ->
let value = eval expr env in
Env.set env key value;
value
| T.List
{ T.value =
[ T.Symbol { T.value = "define-syntax" }
; keyword
; T.List { T.value = transformer }
]
} ->
print_endline ("define-syntax: " ^ Printer.print keyword true);
print_endline
(" transformer: " ^ Printer.dump transformer);
let macro =
Types.list transformer
in
Env.set env keyword macro;
macro
| T.List
{ T.value =
[ T.Symbol { T.value = "lambda" }; T.Vector { T.value = arg_names }; expr ]
}
| T.List
{ T.value =
[ T.Symbol { T.value = "lambda" }; T.List { T.value = arg_names }; expr ]
} ->
Types.proc (function args ->
let sub_env = Env.make (Some env) in
let rec bind_args a b =
match a, b with
| [ T.Symbol { T.value = "." }; name ], args ->
Env.set sub_env name (Types.list args)
| name :: names, arg :: args ->
Env.set sub_env name arg;
bind_args names args
| [], [] -> ()
| _ -> raise (Reader.Syntax_error "wrong parameter count for lambda")
in
bind_args arg_names args;
eval expr sub_env)
(* Can these be replace with define-syntax stuff? *)
| T.List
{ T.value = [ T.Symbol { T.value = "let" }; T.Vector { T.value = bindings }; body ]
}
| T.List
{ T.value = [ T.Symbol { T.value = "let" }; T.List { T.value = bindings }; body ] }
->
let sub_env = Env.make (Some env) in
let rec bind_pairs = function
| T.List { T.value = [ T.Symbol { T.value = sym }; expr ] } :: more ->
let value = eval expr env in
Env.set env (Types.symbol sym) value;
bind_pairs more
| _ -> ()
in
bind_pairs bindings;
eval body sub_env
| T.List { T.value = T.Symbol { T.value = "begin" } :: body } ->
List.fold_left (fun x expr -> eval expr env) T.Nil body
| T.List { T.value = [ T.Symbol { T.value = "if" }; cond; then_expr; else_expr ] } ->
if Types.to_bool (eval cond env) then eval then_expr env else eval else_expr env
| T.List { T.value = [ T.Symbol { T.value = "if" }; cond; then_expr ] } ->
if Types.to_bool (eval cond env) then eval then_expr env else T.Nil
| T.List { T.value = [ T.Symbol { T.value = "quote" }; ast ] } -> ast
| T.List { T.value = [ T.Symbol { T.value = "quasiquote" }; ast ] } ->
eval (quasiquote ast) env
| T.List _ ->
(match eval_ast ast env with
| T.List { T.value = T.Proc { T.value = f } :: args } -> f args
| _ as x ->
raise (Reader.Syntax_error ("'" ^ Printer.print x true ^ "' not a function")))
| _ -> eval_ast ast env
;;
let nameplate = "Martian9 Scheme v0.1"
let read str = Reader.read str
let print exp = Printer.print exp true
let rep str env = print (eval (read str) env)
let rec main =
try
Core.init Core.base;
Env.set
repl_env
(Types.symbol "eval")
(Types.proc (function
| [ ast ] -> eval ast repl_env
| _ -> T.Nil));
ignore
(rep
"(define load-file (lambda (f) (eval (read-string (string \"(begin \" (slurp f) \
\")\")))))"
repl_env);
if Array.length Sys.argv > 1
then print_endline (rep ("(load-file \"" ^ Sys.argv.(1) ^ "\")") repl_env)
else (
print_endline nameplate;
while true do
print_string "m9> ";
let line = read_line () in
try print_endline (rep line repl_env) with
| End_of_file -> ()
| Invalid_argument x ->
output_string stderr ("Invalid argument: " ^ x ^ "\n");
flush stderr
done)
with
| End_of_file -> ()
;;