shithub: martian9

ref: 5fa933a00fd0a280221439c54a246600d454ecc7
dir: /m9.ml/

View raw version
(*
  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 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)
 *)

module T = Types.Types

let repl_env = Env.make (Some Core.base)

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 ast with
  | T.List { T.value = [] } -> ast
  | 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 = "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
      | sym :: expr :: more ->
        Env.set sub_env sym (eval expr sub_env);
        bind_pairs more
      | [ _ ] -> raise (Invalid_argument "let missing body")
      | [] -> ()
    in
    bind_pairs bindings;
    eval body sub_env
  | 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 (Invalid_argument "wrong parameter count")
        in
        bind_args arg_names args;
        eval expr 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 _ ->
    (match eval_ast ast env with
    | T.List { T.value = T.Proc { T.value = f } :: args } -> f args
    | _ -> raise (Invalid_argument "not a function"))
  | _ -> eval_ast ast env
;;

let nameplate = "Martian9 Scheme v0.1"
let read str = Reader.read_str 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;
    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 -> ()
;;