shithub: martian9

Download patch

ref: f76fdc38abf1e1d7a7dfb31ffc297af4294c876f
parent: 5fa933a00fd0a280221439c54a246600d454ecc7
author: smazga <smazga@greymanlabs.com>
date: Fri Aug 7 12:37:42 EDT 2020

making progress

--- a/core.ml
+++ b/core.ml
@@ -17,7 +17,20 @@
 let mk_num x = Types.number x
 let mk_bool x = T.Bool x
 
+let seq = function
+  | T.List { T.value = xs } -> xs
+  | T.Vector { T.value = xs } -> xs
+  | _ -> []
+;;
+
 let init env =
+  Env.set
+    env
+    (Types.symbol "*arguments*")
+    (Types.list
+       (if Array.length Sys.argv > 1
+       then List.map (fun x -> T.String x) (List.tl (List.tl (Array.to_list Sys.argv)))
+       else []));
   Env.set env (Types.symbol "+") (number_compare mk_num ( +. ));
   Env.set env (Types.symbol "-") (number_compare mk_num ( -. ));
   Env.set env (Types.symbol "*") (number_compare mk_num ( *. ));
@@ -59,5 +72,41 @@
     (Types.proc (function
         | [ T.List { T.value = xs } ] | [ T.Vector { T.value = xs } ] ->
           Types.number (float_of_int (List.length xs))
-        | _ -> Types.number 0.))
+        | _ -> Types.number 0.));
+  Env.set
+    env
+    (Types.symbol "display")
+    (Types.proc (function xs ->
+         T.String (String.concat " " (List.map (fun s -> Printer.print s false) xs))));
+  Env.set
+    env
+    (Types.symbol "string")
+    (Types.proc (function xs ->
+         T.String (String.concat "" (List.map (fun s -> Printer.print s false) xs))));
+  Env.set
+    env
+    (Types.symbol "read-string")
+    (Types.proc (function
+        | [ T.String x ] -> Reader.read x
+        | _ -> T.Nil));
+  Env.set
+    env
+    (Types.symbol "slurp")
+    (Types.proc (function
+        | [ T.String x ] -> T.String (Reader.slurp x)
+        | _ -> T.Nil));
+  Env.set env (Types.symbol "cons") (Types.proc (function xs -> Types.list xs));
+  (* | x :: xs -> Types.pair x xs
+   * | _ -> T.Nil)); *)
+  Env.set
+    env
+    (Types.symbol "concat")
+    (Types.proc
+       (let rec concat = function
+          | x :: y :: more -> concat (Types.list (seq x @ seq y) :: more)
+          | [ (T.List _ as x) ] -> x
+          | [ x ] -> Types.list (seq x)
+          | [] -> Types.list []
+        in
+        concat))
 ;;
--- a/env.ml
+++ b/env.ml
@@ -31,6 +31,6 @@
   | T.Symbol { T.value = key } ->
     (match find env sym with
     | Some found_env -> Data.find key !(found_env.data)
-    | None -> raise (Invalid_argument ("'" ^ key ^ "' not found")))
+    | None -> raise (Invalid_argument ("unknown symbol '" ^ key ^ "'")))
   | _ -> raise (Invalid_argument "get: not a symbol")
 ;;
--- a/m9.ml
+++ b/m9.ml
@@ -14,6 +14,23 @@
 
 let repl_env = Env.make (Some Core.base)
 
+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 rec eval_ast ast env =
   match ast with
   | T.Symbol s -> Env.get env ast
@@ -74,6 +91,9 @@
     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
@@ -82,7 +102,7 @@
 ;;
 
 let nameplate = "Martian9 Scheme v0.1"
-let read str = Reader.read_str str
+let read str = Reader.read str
 let print exp = Printer.print exp true
 let rep str env = print (eval (read str) env)
 
@@ -89,16 +109,30 @@
 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
+    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 -> ()
 ;;
--- a/mkfile
+++ b/mkfile
@@ -1,14 +1,14 @@
 BIN=m9
 
-# $BIN: types.cmx env.cmx core.cmx printer.cmx reader.cmx $BIN.cmx
-# 	ocamlopt -o $target str.cmxa $prereq
+FILES=\
+	types.ml\
+	env.ml\
+	reader.ml\
+	printer.ml\
+	core.ml
 
-$BIN: types.cmo env.cmo core.cmo printer.cmo reader.cmo $BIN.cmo
-	ocamlc -o $target str.cma $prereq
-
-$BIN.cmo : core.cmo printer.cmo reader.cmo
-env.cmo printer.cmo reader.cmo : types.cmo
-core.cmo : env.cmo
+$BIN:
+	ocamlc str.cma -o $target $FILES m9.ml
 
 %.cmx : %.ml
 	ocamlopt -c $stem.ml
--- a/notes.org
+++ b/notes.org
@@ -1,8 +1,14 @@
 * First things:
-** implement cons
+** TODO need an "unspecified" type?
+** TODO (display) should return unspecified
+** TODO implement (cons)
 Pairs should be preserved, I think
+Also, it should _only_ be pairs, nothing more.
+** TODO (define) needs to support function definitions
+Right now you need to use lambda
 * Read
-** quote and quasiquote symbols not supported
+** TODO quote and quasiquote symbols not supported
+The shortcuts work, but not the keywords
 * Eval
 * Print
 * Things to watch for
--- a/printer.ml
+++ b/printer.ml
@@ -18,8 +18,7 @@
   | T.Char c -> Char.escaped c
   | T.Nil -> "nil"
   | T.Comment -> "" (* TODO: this leaves a space in the output for block comments *)
-  (* | T.Pair { T.value = one, two } -> "(" ^ one ^ " . " ^ two ^ ")" *)
-  | T.Pair (p, q) -> "<pair unsupported>"
+  (* | T.Pair ({ T.value = one }, { T.value = two }) -> "(" ^ (print one readable) ^ " . " ^ (print two readable) ^ ")" *)
   | T.Proc p -> "#<proc>"
   | T.Symbol { T.value = s } -> s
   | T.Bytevector bv -> "<bytevector unsupported>"
@@ -29,7 +28,18 @@
     then string_of_float n.value
     else string_of_int (int_of_float n.value)
   | T.Port p -> "<port unsupported>"
-  | T.String s -> s (* need to handle escaping and stuff *)
+  | T.String s ->
+    if r
+    then
+      "\""
+      ^ Reader.gsub
+          (Str.regexp "\\([\"\\\n]\\)")
+          (function
+            | "\n" -> "\\n"
+            | x -> "\\" ^ x)
+          s
+      ^ "\""
+    else s
   | T.List { T.value = xs } ->
     "(" ^ String.concat " " (List.map (fun s -> print s r) xs) ^ ")"
   | T.Vector { T.value = v } ->
--- a/reader.ml
+++ b/reader.ml
@@ -28,6 +28,33 @@
        (Str.full_split token_re str))
 ;;
 
+(* copied verbatim - must needs grok *)
+let gsub re f str =
+  String.concat
+    ""
+    (List.map
+       (function
+         | Str.Delim x -> f x
+         | Str.Text x -> x)
+       (Str.full_split re str))
+;;
+
+let unescape_string token =
+  if Str.string_match string_re token 0
+  then (
+    let without_quotes = String.sub token 1 (String.length token - 2) in
+    gsub
+      (Str.regexp "\\\\.")
+      (function
+        | "\\n" -> "\n"
+        | x -> String.sub x 1 1)
+      without_quotes)
+  else (
+    output_string stderr "expected '\"', got EOF\n";
+    flush stderr;
+    raise End_of_file)
+;;
+
 let read_atom token =
   match token with
   | "null" -> T.Nil
@@ -43,7 +70,7 @@
         (match token.[1] with
         | '0' .. '9' -> Types.number (float_of_string token)
         | _ -> Types.symbol token))
-    | '"' -> T.String token (* TODO: unescape *)
+    | '"' -> T.String (unescape_string token)
     | _ -> Types.symbol token)
 ;;
 
@@ -61,11 +88,17 @@
         eol
         { list_form = list_reader.list_form @ [ reader.form ]; tokens = reader.tokens })
 
+and read_quote sym tokens =
+  let reader = read_form tokens in
+  { form = Types.list [ Types.symbol sym; reader.form ]; tokens = reader.tokens }
+
 and read_form all_tokens =
   match all_tokens with
   | [] -> raise End_of_file
   | token :: tokens ->
     (match token with
+    | "'" -> read_quote "quote" tokens
+    | "`" -> read_quote "quasiquote" tokens
     | "(" ->
       let list_reader = read_list ")" { list_form = []; tokens } in
       { form = Types.list list_reader.list_form; tokens = list_reader.tokens }
@@ -76,4 +109,12 @@
       if token.[0] = ';' then read_form tokens else { form = read_atom token; tokens })
 ;;
 
-let read_str str = (read_form (tokenize str)).form
+let slurp filename =
+  let chan = open_in filename in
+  let b = Buffer.create 27 in
+  Buffer.add_channel b chan (in_channel_length chan);
+  close_in chan;
+  Buffer.contents b
+;;
+
+let read str = (read_form (tokenize str)).form
--- a/types.ml
+++ b/types.ml
@@ -10,7 +10,7 @@
     | Char of char
     | Nil
     | Comment
-    | Pair of t * t
+    (* | Pair of t with_meta * t list *)
     | Proc of (t list -> t) with_meta
     | Symbol of string with_meta
     | Bytevector of t list
@@ -47,6 +47,8 @@
 ;;
 
 let list x = Types.List { Types.value = x; meta = Types.Nil }
+
+(* let pair x xs = Types.Pair ({ Types.value = x; meta = Types.Nil }, Types.List { Types.value = xs; meta = Types.Nil }) *)
 let proc x = Types.Proc { Types.value = x; meta = Types.Nil }
 let symbol x = Types.Symbol { Types.value = x; meta = Types.Nil }
 let vector x = Types.Vector { Types.value = x; meta = Types.Nil }