Scala: all steps 0-9 but no metadata.
authorJoel Martin <github@martintribe.org>
Fri, 19 Dec 2014 05:21:39 +0000 (23:21 -0600)
committerJoel Martin <github@martintribe.org>
Fri, 9 Jan 2015 22:16:51 +0000 (16:16 -0600)
20 files changed:
.gitignore
Makefile
README.md
docs/TODO
scala/build.sbt [new file with mode: 0644]
scala/core.scala [new file with mode: 0644]
scala/env.scala [new file with mode: 0644]
scala/printer.scala [new file with mode: 0644]
scala/reader.scala [new file with mode: 0644]
scala/step0_repl.scala [new file with mode: 0644]
scala/step1_read_print.scala [new file with mode: 0644]
scala/step2_eval.scala [new file with mode: 0644]
scala/step3_env.scala [new file with mode: 0644]
scala/step4_if_fn_do.scala [new file with mode: 0644]
scala/step5_tco.scala [new file with mode: 0644]
scala/step6_file.scala [new file with mode: 0644]
scala/step7_quote.scala [new file with mode: 0644]
scala/step8_macros.scala [new file with mode: 0644]
scala/step9_try.scala [new file with mode: 0644]
scala/types.scala [new file with mode: 0644]

index 4b638de..2926e3d 100644 (file)
@@ -35,3 +35,5 @@ rust/.cargo
 r/lib
 vb/*.exe
 vb/*.dll
+scala/target
+scala/project
index bd25ad7..7716a44 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ PYTHON = python
 #
 
 IMPLS = bash c clojure coffee cs go java js make mal perl php ps \
-       python r ruby rust vb
+       python r ruby rust scala vb
 
 step0 = step0_repl
 step1 = step1_read_print
@@ -65,6 +65,7 @@ python_STEP_TO_PROG =  python/$($(1)).py
 r_STEP_TO_PROG =       r/$($(1)).r
 ruby_STEP_TO_PROG =    ruby/$($(1)).rb
 rust_STEP_TO_PROG =    rust/target/$($(1))
+scala_STEP_TO_PROG =   scala/$($(1)).scala
 vb_STEP_TO_PROG =      vb/$($(1)).exe
 
 
@@ -85,6 +86,7 @@ python_RUNSTEP =  $(PYTHON) ../$(2) $(3)
 r_RUNSTEP =       Rscript ../$(2) $(3)
 ruby_RUNSTEP =    ruby ../$(2) $(3)
 rust_RUNSTEP =    ../$(2) $(3)
+scala_RUNSTEP =   sbt 'run-main $($(1))$(if $(3), $(3),)'
 vb_RUNSTEP =      mono ../$(2) --raw $(3)
 
 # Extra options to pass to runtest.py
index e3a63ae..7ed6a7b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ language. Mal is implemented from scratch in 18 different languages:
 * R
 * Ruby
 * Rust
+* Scala
 * Visual Basic.NET
 
 
@@ -214,6 +215,18 @@ cargo build
 ./target/stepX_YYY
 ```
 
+### Scala ###
+
+Install scala and sbt (http://www.scala-sbt.org/0.13/tutorial/Installing-sbt-on-Linux.html):
+
+```
+cd scala
+sbt 'run-main stepX_YYY'
+    # OR
+sbt compile
+scala -classpath target/scala*/classes stepX_YYY
+```
+
 ### Visual Basic.NET ###
 
 The VB.NET implementation of mal has been tested on Linux using the Mono
index 0ff76a5..633a3c5 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
@@ -122,6 +122,10 @@ Future Implementations:
         - http://groovy-lang.org/learn.html
         - http://groovy-lang.org/structure.html
 
+    - Scala
+        - aptitude install scala
+        - http://learnxinyminutes.com/docs/scala/
+
     - Visual Basic
         aptitude install mono-vbnc
 
diff --git a/scala/build.sbt b/scala/build.sbt
new file mode 100644 (file)
index 0000000..d2a2802
--- /dev/null
@@ -0,0 +1,15 @@
+lazy val root = (project in file(".")).
+  settings(
+    name := "mal",
+    version := "0.1",
+    scalaVersion := "2.11.4"
+  )
+
+// Suppress message for command line execution
+
+onLoadMessage := ""
+
+showSuccess := false
+
+logLevel in runMain := Level.Warn
+
diff --git a/scala/core.scala b/scala/core.scala
new file mode 100644 (file)
index 0000000..6310f89
--- /dev/null
@@ -0,0 +1,178 @@
+import scala.collection.mutable
+import scala.io.Source
+
+import printer._pr_list
+
+object core {
+  def mal_throw(a: List[Any]) = {
+    throw new types.MalException(printer._pr_str(a(0))).init(a(0))
+  }
+
+  // Scalar functions
+  def keyword(a: List[Any]) = {
+    "\u029e" + a(0).asInstanceOf[String]
+  }
+
+  def keyword_Q(a: List[Any]) = {
+    a(0) match {
+      case s: String => s(0) == '\u029e'
+      case _ => false
+    }
+  }
+
+  // string functions
+  def read_string(a: List[Any]) = {
+    reader.read_str(a(0).asInstanceOf[String])
+  }
+
+  def slurp(a: List[Any]) = {
+    Source.fromFile(a(0).asInstanceOf[String]).getLines.mkString("\n")
+  }
+
+  // Hash Map functions
+  def assoc(a: List[Any]): Any = {
+    a(0).asInstanceOf[Map[String,Any]] ++
+      (types._hash_map(a.drop(1)).asInstanceOf[Map[String,Any]])
+  }
+
+  def dissoc(a: List[Any]): Any = {
+    var kSet = types._toList(a.drop(1)).toSet
+    a(0).asInstanceOf[Map[String,Any]]
+      .filterKeys{ !kSet.contains(_) }
+  }
+
+  def get(a: List[Any]): Any = {
+    val hm = a(0).asInstanceOf[Map[String,Any]]
+    val key = a(1).asInstanceOf[String]
+    if (hm != null && hm.contains(key)) hm(key) else null
+  }
+
+  def contains_Q(a: List[Any]): Any = {
+    a(0).asInstanceOf[Map[String,Any]]
+      .contains(a(1).asInstanceOf[String])
+  }
+
+
+  // sequence functions
+  def concat(a: List[Any]): List[Any] = {
+    (for (sq <- a) yield types._toIter(sq)).flatten
+  }
+
+  def nth(a: List[Any]): Any = {
+    val lst = types._toList(a(0))
+    val idx = a(1).asInstanceOf[Int]
+    if (idx < lst.length) {
+      lst(idx)
+    } else {
+      throw new Exception("nth: index out of range")
+    }
+  }
+
+  def first(a: List[Any]): Any = {
+    val lst = types._toList(a(0))
+    if (lst.length > 0) lst(0) else null
+  }
+
+
+  def apply(a: List[Any]): Any = {
+    a match {
+      case f :: rest => {
+        var args1 = rest.slice(0,rest.length-1)
+        var args = args1 ++ types._toList(rest(rest.length-1))
+        types._apply(f, args)
+      }
+      case _ => throw new Exception("invalid apply call")
+    }
+  }
+
+  def do_map(a: List[Any]): Any = {
+    a match {
+      case f :: seq :: Nil => {
+        types._toList(seq).map(x => types._apply(f,List(x)))
+      }
+      case _ => throw new Exception("invalid map call")
+    }
+  }
+
+
+  // atom functions
+  def reset_BANG(a: List[Any]): Any = {
+    a(0).asInstanceOf[types.Atom].value = a(1)
+    a(1)
+  }
+
+  def swap_BANG(a: List[Any]): Any = {
+    a match {
+      case a0 :: f :: rest => {
+        val atm = a0.asInstanceOf[types.Atom]
+        val args = atm.value +: rest
+        atm.value = types._apply(f, args)
+        atm.value
+      }
+      case _ => throw new Exception("invalid swap! call")
+    }
+  }
+
+
+  val ns: Map[String, Any] = Map(
+    "="  -> ((a: List[Any]) => types._equal_Q(a(0), a(1))),
+    "throw" -> mal_throw _,
+    "nil?" -> ((a: List[Any]) => a(0) == null),
+    "true?" -> ((a: List[Any]) => a(0) == true),
+    "false?" -> ((a: List[Any]) => a(0) == false),
+    "symbol" -> ((a: List[Any]) => Symbol(a(0).asInstanceOf[String])),
+    "symbol?" -> ((a: List[Any]) => a(0).isInstanceOf[Symbol]),
+    "keyword" -> keyword _,
+    "keyword?" -> keyword_Q _,
+
+    "pr-str" -> ((a: List[Any]) => _pr_list(a, true, " ")),
+    "str" -> ((a: List[Any]) => _pr_list(a, false, "")),
+    "prn" -> ((a: List[Any]) => { println(_pr_list(a, true, " ")); null}),
+    "println" -> ((a: List[Any]) => { println(_pr_list(a, false, " ")); null}),
+    "read-string" -> read_string _,
+    "slurp" -> slurp _,
+    "<"  -> ((a: List[Any]) => a(0).asInstanceOf[Int] <  a(1).asInstanceOf[Int]),
+    "<=" -> ((a: List[Any]) => a(0).asInstanceOf[Int] <= a(1).asInstanceOf[Int]),
+    ">"  -> ((a: List[Any]) => a(0).asInstanceOf[Int] >  a(1).asInstanceOf[Int]),
+    ">=" -> ((a: List[Any]) => a(0).asInstanceOf[Int] >= a(1).asInstanceOf[Int]),
+    "+"  -> ((a: List[Any]) => a(0).asInstanceOf[Int] + a(1).asInstanceOf[Int]),
+    "-"  -> ((a: List[Any]) => a(0).asInstanceOf[Int] - a(1).asInstanceOf[Int]),
+    "*"  -> ((a: List[Any]) => a(0).asInstanceOf[Int] * a(1).asInstanceOf[Int]),
+    "/"  -> ((a: List[Any]) => a(0).asInstanceOf[Int] / a(1).asInstanceOf[Int]),
+
+    "list" -> ((a: List[Any]) => a),
+    "list?" -> ((a: List[Any]) => a(0).isInstanceOf[List[Any]]),
+    "vector" -> ((a: List[Any]) => a.toArray),
+    "vector?" -> ((a: List[Any]) => a(0).isInstanceOf[Array[Any]]),
+    "hash-map" -> ((a: List[Any]) => types._hash_map(a)),
+    "map?" -> ((a: List[Any]) => a(0).isInstanceOf[Map[String,Any] @unchecked]),
+    "assoc" -> assoc _,
+    "dissoc" -> dissoc _,
+    "get" -> get _,
+    "contains?" -> contains_Q _,
+    "keys" -> ((a: List[Any]) => a(0).asInstanceOf[Map[String,Any]].keys.toList),
+    "vals" -> ((a: List[Any]) => a(0).asInstanceOf[Map[String,Any]].values.toList),
+
+    "sequential?" -> ((a: List[Any]) => types._sequential_Q(a(0))),
+    "cons" -> ((a: List[Any]) => a(0) +: types._toList(a(1))),
+    "concat" -> concat _,
+    "nth" -> nth _,
+    "first" -> first _,
+    "rest" -> ((a: List[Any]) => types._toList(a(0)).drop(1)),
+    "empty?" -> ((a: List[Any]) => types._toIter(a(0)).isEmpty),
+    "count" -> ((a: List[Any]) => types._toIter(a(0)).length),
+    "conj" -> ((a: List[Any]) => null),
+    "apply" -> apply _,
+    "map" -> do_map _,
+
+    "with-meta" -> ((a: List[Any]) => null),
+    "meta" -> ((a: List[Any]) => null),
+    "atom" -> ((a: List[Any]) => new types.Atom(a(0))),
+    "atom?" -> ((a: List[Any]) => a(0).isInstanceOf[types.Atom]),
+    "deref" -> ((a: List[Any]) => a(0).asInstanceOf[types.Atom].value),
+    "reset!" -> reset_BANG _,
+    "swap!" -> swap_BANG _
+    )
+}
+
+// vim:ts=2:sw=2
diff --git a/scala/env.scala b/scala/env.scala
new file mode 100644 (file)
index 0000000..9b6cff8
--- /dev/null
@@ -0,0 +1,40 @@
+import scala.collection.mutable
+
+object env {
+  class Env(outer: Env = null,
+            binds: Iterator[Any] = null,
+            exprs: Iterator[Any] = null) {
+    val data: mutable.Map[Symbol, Any] = mutable.Map()
+    if (binds != null && exprs != null) {
+      binds.foreach(b => {
+        val k = b.asInstanceOf[Symbol]
+        if (k == '&) {
+          data(binds.next().asInstanceOf[Symbol]) = exprs.toList
+        } else {
+          data(k) = exprs.next()
+        }
+      })
+    }
+
+    def find(key: Symbol): Env = {
+        if (data.contains(key)) {
+            this
+        } else if (outer != null) {
+            outer.find(key)
+        } else {
+            null
+        }
+    }
+    def set(key: Symbol, value: Any): Any = {
+        data(key) = value
+        value
+    }
+    def get(key: Symbol): Any = {
+        val env = find(key)
+        if (env == null) throw new Exception("'" + key.name + "' not found")
+        env.data(key)
+    }
+  }
+}
+
+// vim:ts=2:sw=2
diff --git a/scala/printer.scala b/scala/printer.scala
new file mode 100644 (file)
index 0000000..74b1433
--- /dev/null
@@ -0,0 +1,45 @@
+import types.Function
+
+object printer {
+  def _pr_str(obj: Any, print_readably: Boolean = true): String = {
+    val _r = print_readably
+    return obj match {
+      case l: List[Any]   => "(" + l.map(_pr_str(_, _r)).mkString(" ") + ")"
+      case v: Array[Any]  => "[" + v.map(_pr_str(_, _r)).mkString(" ") + "]"
+      case m: Map[String @unchecked,Any @unchecked] => {
+        val lst = m.map{case (k,v) => List(k, v)}.flatten
+        "{" + lst.map(_pr_str(_,_r)).mkString(" ") + "}"
+      }
+      case s: String      => {
+        if (s.length > 0 && s(0) == '\u029e') {
+          ":" + s.substring(1,s.length)
+        } else if (_r) {
+          //println("here1: " + s)
+          "\"" + s.replace("\\", "\\\\")
+                  .replace("\"", "\\\"")
+                  .replace("\n", "\\n") + "\""
+        } else {
+          s
+        }
+      }
+      case Symbol(s)      => s
+      case a: types.Atom  => "(atom " + a.value + ")"
+      case null           => "nil"
+      case _              => {
+        if (obj.isInstanceOf[Function]) {
+          val f = obj.asInstanceOf[Function]
+          "<function (fn* " + _pr_str(f.params) + " " + _pr_str(f.ast) + ")>"
+        } else {
+          obj.toString
+        }
+      }
+    }
+  }
+
+  def _pr_list(lst: List[Any], print_readably: Boolean = true,
+               sep: String = " "): String = {
+    lst.map{_pr_str(_, print_readably)}.mkString(sep)
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/reader.scala b/scala/reader.scala
new file mode 100644 (file)
index 0000000..fc16f28
--- /dev/null
@@ -0,0 +1,88 @@
+import scala.util.matching.Regex
+
+object reader {
+
+  class Reader (tokens: Array[String]) {
+    var data = tokens
+    var position: Int = 0
+    def peek(): String = {
+      if (position >= data.length) return(null)
+      data(position)
+    }
+    def next(): String = {
+      if (position >= data.length) return(null)
+      position = position + 1
+      data(position-1)
+    }
+  }
+
+  def tokenize(str: String): Array[String] = {
+    val re = """[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)""".r
+    re.findAllMatchIn(str).map{ _.group(1) }
+                          .filter{ s => s != "" && s(0) != ';' }
+                          .toArray
+  }
+
+  def parse_str(s: String): String = {
+    s.replace("\\\"", "\"").replace("\\n", "\n")
+  }
+
+  def read_atom(rdr: Reader): Any = {
+    val token = rdr.next()
+    val re_int = """^(-?[0-9]+)$""".r
+    val re_flt = """^(-?[0-9][0-9.]*)$""".r
+    val re_str =  """^"(.*)"$""".r
+    val re_key = """^:(.*)$""".r
+    return token match {
+      case re_int(i) => i.toInt       // integer
+      case re_flt(f) => f.toDouble    // float
+      case re_str(s) => parse_str(s)  // string
+      case re_key(k) => "\u029e" + k  // keyword
+      case "nil"     => null
+      case "true"    => true
+      case "false"   => false
+      case _         => Symbol(token) // symbol
+    }
+  }
+
+  def read_list(rdr: Reader,
+                start: String = "(", end: String = ")"): List[Any] = {
+    var ast: List[Any] = List()
+    var token = rdr.next()
+    if (token != start) throw new Exception("expected '" + start + "', got EOF")
+    while ({token = rdr.peek(); token != end}) {
+      if (token == null) throw new Exception("expected '" + end + "', got EOF")
+      ast = ast :+ read_form(rdr)
+    }
+    rdr.next()
+    ast
+  }
+
+  def read_form(rdr: Reader): Any = {
+    return rdr.peek() match {
+      case "'"  => { rdr.next; List(Symbol("quote"), read_form(rdr)) }
+      case "`"  => { rdr.next; List(Symbol("quasiquote"), read_form(rdr)) }
+      case "~"  => { rdr.next; List(Symbol("unquote"), read_form(rdr)) }
+      case "~@" => { rdr.next; List(Symbol("splice-unquote"), read_form(rdr)) }
+      case "^"  => { rdr.next; val meta = read_form(rdr);
+                     List(Symbol("with-meta"), read_form(rdr), meta) }
+      case "@"  => { rdr.next; List(Symbol("deref"), read_form(rdr)) }
+
+      case "("  => read_list(rdr)
+      case ")"  => throw new Exception("unexpected ')')")
+      case "["  => read_list(rdr, "[", "]").toArray
+      case "]"  => throw new Exception("unexpected ']')")
+      case "{"  => types._hash_map(read_list(rdr, "{", "}"))
+      case "}"  => throw new Exception("unexpected '}')")
+      case _    => read_atom(rdr)
+    }
+  }
+
+  def read_str(str: String): Any = {
+    val tokens = tokenize(str)
+    if (tokens.length == 0) return null
+    return read_form(new Reader(tokens))
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step0_repl.scala b/scala/step0_repl.scala
new file mode 100644 (file)
index 0000000..5ec94fe
--- /dev/null
@@ -0,0 +1,33 @@
+object step0_repl {
+  def READ(str: String): String = {
+    str
+  }
+
+  def EVAL(str: String, env: String): String = {
+    str
+  }
+
+  def PRINT(str: String): String = {
+    str
+  }
+
+  def REP(str: String): String = {
+    PRINT(EVAL(READ(str), ""))
+  }
+
+  def main(args: Array[String]) {
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Exception => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step1_read_print.scala b/scala/step1_read_print.scala
new file mode 100644 (file)
index 0000000..7485eb0
--- /dev/null
@@ -0,0 +1,39 @@
+import reader.tokenize
+
+object step1_read_print {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def EVAL(ast: Any, env: String): Any = {
+    ast
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val REP = (str: String) => {
+      PRINT(EVAL(READ(str), ""))
+    }
+
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Exception => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step2_eval.scala b/scala/step2_eval.scala
new file mode 100644 (file)
index 0000000..b7b209d
--- /dev/null
@@ -0,0 +1,73 @@
+import reader.tokenize
+
+object step2_eval {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def eval_ast(ast: Any, env: Map[Symbol,Any]): Any = {
+    ast match {
+      case s : Symbol    => env(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(ast: Any, env: Map[Symbol,Any]): Any = {
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    eval_ast(ast, env) match {
+      case f :: el => {
+        var fn: List[Any] => Any = null
+        try {
+          fn = f.asInstanceOf[(List[Any]) => Any]
+        } catch {
+          case _: Throwable =>
+            throw new Exception("attempt to call non-function")
+        }
+        return fn(el)
+      }
+      case _ => throw new Exception("invalid apply")
+    }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Map[Symbol,Any] = Map(
+      '+ -> ((a: List[Any]) => a(0).asInstanceOf[Int] + a(1).asInstanceOf[Int]),
+      '- -> ((a: List[Any]) => a(0).asInstanceOf[Int] - a(1).asInstanceOf[Int]),
+      '* -> ((a: List[Any]) => a(0).asInstanceOf[Int] * a(1).asInstanceOf[Int]),
+      '/ -> ((a: List[Any]) => a(0).asInstanceOf[Int] / a(1).asInstanceOf[Int]))
+    val REP = (str: String) => {
+      PRINT(EVAL(READ(str), repl_env))
+    }
+
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Exception => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step3_env.scala b/scala/step3_env.scala
new file mode 100644 (file)
index 0000000..12d335d
--- /dev/null
@@ -0,0 +1,93 @@
+import env.Env
+
+object step3_env {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(ast: Any, env: Env): Any = {
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        val it: Iterator[Any] = a1 match {
+          case l: List[Any] => l.iterator
+          case v: Array[Any] => v.iterator
+          case _ => throw new Exception("let* non-sequence bindings")
+        }
+        for (g <- it.grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        return EVAL(a2, let_env)
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            var fn: List[Any] => Any = null
+            try {
+              fn = f.asInstanceOf[(List[Any]) => Any]
+            } catch {
+              case _: Throwable =>
+                throw new Exception("attempt to call non-function")
+            }
+            return fn(el)
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    repl_env.set('+, (a: List[Any]) => a(0).asInstanceOf[Int] + a(1).asInstanceOf[Int])
+    repl_env.set('-, (a: List[Any]) => a(0).asInstanceOf[Int] - a(1).asInstanceOf[Int])
+    repl_env.set('*, (a: List[Any]) => a(0).asInstanceOf[Int] * a(1).asInstanceOf[Int])
+    repl_env.set('/, (a: List[Any]) => a(0).asInstanceOf[Int] / a(1).asInstanceOf[Int])
+    val REP = (str: String) => {
+      PRINT(EVAL(READ(str), repl_env))
+    }
+
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Exception => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step4_if_fn_do.scala b/scala/step4_if_fn_do.scala
new file mode 100644 (file)
index 0000000..a7a6b22
--- /dev/null
@@ -0,0 +1,106 @@
+import env.Env
+
+object step4_if_fn_do {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(ast: Any, env: Env): Any = {
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        for (g <- types._toIter(a1).grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        return EVAL(a2, let_env)
+      }
+      case Symbol("do") :: rest => {
+        val el = eval_ast(rest, env)
+        return el.asInstanceOf[List[Any]].last
+      }
+      case Symbol("if") :: a1 :: a2 :: rest => {
+        val cond = EVAL(a1, env)
+        if (cond == null || cond == false) {
+          if (rest.length == 0) return null
+          return EVAL(rest(0), env)
+        } else {
+          return EVAL(a2, env)
+        }
+      }
+      case Symbol("fn*") :: a1 :: a2 :: Nil => {
+        return (args: List[Any]) => {
+          EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+        }
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            var fn: List[Any] => Any = null
+            try {
+              fn = f.asInstanceOf[(List[Any]) => Any]
+            } catch {
+              case _: Throwable =>
+                throw new Exception("attempt to call non-function")
+            }
+            return fn(el)
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+    // core.scala: defined using scala
+    core.ns.map{case (k: String,v: Any) => { repl_env.set(Symbol(k), v) }}
+
+    // core.mal: defined using the language itself
+    REP("(def! not (fn* (a) (if a false true)))")
+
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Exception => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step5_tco.scala b/scala/step5_tco.scala
new file mode 100644 (file)
index 0000000..d54eaa7
--- /dev/null
@@ -0,0 +1,117 @@
+import env.Env
+
+object step5_tco {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(orig_ast: Any, orig_env: Env): Any = {
+   var ast = orig_ast; var env = orig_env;
+   while (true) {
+
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        for (g <- types._toIter(a1).grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        env = let_env
+        ast = a2   // continue loop (TCO)
+      }
+      case Symbol("do") :: rest => {
+        eval_ast(rest.slice(1,rest.length-1), env)
+        ast = ast.asInstanceOf[List[Any]].last  // continue loop (TCO)
+      }
+      case Symbol("if") :: a1 :: a2 :: rest => {
+        val cond = EVAL(a1, env)
+        if (cond == null || cond == false) {
+          if (rest.length == 0) return null
+          ast = rest(0)  // continue loop (TCO)
+        } else {
+          ast = a2  // continue loop (TCO)
+        }
+      }
+      case Symbol("fn*") :: a1 :: a2 :: Nil => {
+        return new types.Function(a2, env, a1.asInstanceOf[List[Any]],
+          (args: List[Any]) => {
+            EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+          }
+        )
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            f match {
+              case fn: types.Function => {
+                env = fn.gen_env(el) 
+                ast = fn.ast  // continue loop (TCO)
+              }
+              case fn: ((List[Any]) => Any) @unchecked => {
+                return fn(el)
+              }
+              case _ => {
+                throw new Exception("attempt to call non-function")
+              }
+            }
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+   }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+    // core.scala: defined using scala
+    core.ns.map{case (k: String,v: Any) => { repl_env.set(Symbol(k), v) }}
+
+    // core.mal: defined using the language itself
+    REP("(def! not (fn* (a) (if a false true)))")
+
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Throwable => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step6_file.scala b/scala/step6_file.scala
new file mode 100644 (file)
index 0000000..0b4d228
--- /dev/null
@@ -0,0 +1,126 @@
+import env.Env
+
+object step6_file {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(orig_ast: Any, orig_env: Env): Any = {
+   var ast = orig_ast; var env = orig_env;
+   while (true) {
+
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        for (g <- types._toIter(a1).grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        env = let_env
+        ast = a2   // continue loop (TCO)
+      }
+      case Symbol("do") :: rest => {
+        eval_ast(rest.slice(0,rest.length-1), env)
+        ast = ast.asInstanceOf[List[Any]].last  // continue loop (TCO)
+      }
+      case Symbol("if") :: a1 :: a2 :: rest => {
+        val cond = EVAL(a1, env)
+        if (cond == null || cond == false) {
+          if (rest.length == 0) return null
+          ast = rest(0)  // continue loop (TCO)
+        } else {
+          ast = a2  // continue loop (TCO)
+        }
+      }
+      case Symbol("fn*") :: a1 :: a2 :: Nil => {
+        return new types.Function(a2, env, a1.asInstanceOf[List[Any]],
+          (args: List[Any]) => {
+            EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+          }
+        )
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            f match {
+              case fn: types.Function => {
+                env = fn.gen_env(el) 
+                ast = fn.ast  // continue loop (TCO)
+              }
+              case fn: ((List[Any]) => Any) @unchecked => {
+                return fn(el)
+              }
+              case _ => {
+                throw new Exception("attempt to call non-function: " + f)
+              }
+            }
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+   }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+    // core.scala: defined using scala
+    core.ns.map{case (k: String,v: Any) => { repl_env.set(Symbol(k), v) }}
+    repl_env.set(Symbol("eval"), (a: List[Any]) => EVAL(a(0), repl_env))
+    repl_env.set(Symbol("*ARGV*"), args.slice(1,args.length).toList)
+
+    // core.mal: defined using the language itself
+    REP("(def! not (fn* (a) (if a false true)))")
+    REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+
+    if (args.length > 0) {
+      REP("(load-file \"" + args(0) + "\")")
+      System.exit(0)
+    }
+
+    // repl loop
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Throwable => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step7_quote.scala b/scala/step7_quote.scala
new file mode 100644 (file)
index 0000000..1bb617a
--- /dev/null
@@ -0,0 +1,159 @@
+import env.Env
+
+object step7_quote {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def is_pair(x: Any): Boolean = {
+    types._sequential_Q(x) && types._toIter(x).length > 0
+  }
+
+  def quasiquote(ast: Any): Any = {
+    if (!is_pair(ast)) {
+      return List(Symbol("quote"), ast)
+    } else {
+      val a0 = types._toList(ast)(0)
+      if (types._symbol_Q(a0) &&
+          a0.asInstanceOf[Symbol].name == "unquote") {
+        return types._toList(ast)(1)
+      } else if (is_pair(a0)) {
+        val a00 = types._toList(a0)(0)
+        if (types._symbol_Q(a00) &&
+            a00.asInstanceOf[Symbol].name == "splice-unquote") {
+          return List(Symbol("concat"),
+                      types._toList(a0)(1),
+                      quasiquote(types._toList(ast).drop(1)))
+        }
+      }
+      return List(Symbol("cons"),
+                  quasiquote(a0),
+                  quasiquote(types._toList(ast).drop(1)))
+    }
+  }
+
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(orig_ast: Any, orig_env: Env): Any = {
+   var ast = orig_ast; var env = orig_env;
+   while (true) {
+
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        for (g <- types._toIter(a1).grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        env = let_env
+        ast = a2   // continue loop (TCO)
+      }
+      case Symbol("quote") :: a1 :: Nil => {
+        return a1
+      }
+      case Symbol("quasiquote") :: a1 :: Nil => {
+        ast = quasiquote(a1)  // continue loop (TCO)
+      }
+      case Symbol("do") :: rest => {
+        eval_ast(rest.slice(0,rest.length-1), env)
+        ast = ast.asInstanceOf[List[Any]].last  // continue loop (TCO)
+      }
+      case Symbol("if") :: a1 :: a2 :: rest => {
+        val cond = EVAL(a1, env)
+        if (cond == null || cond == false) {
+          if (rest.length == 0) return null
+          ast = rest(0)  // continue loop (TCO)
+        } else {
+          ast = a2  // continue loop (TCO)
+        }
+      }
+      case Symbol("fn*") :: a1 :: a2 :: Nil => {
+        return new types.Function(a2, env, a1.asInstanceOf[List[Any]],
+          (args: List[Any]) => {
+            EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+          }
+        )
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            f match {
+              case fn: types.Function => {
+                env = fn.gen_env(el) 
+                ast = fn.ast  // continue loop (TCO)
+              }
+              case fn: ((List[Any]) => Any) @unchecked => {
+                return fn(el)
+              }
+              case _ => {
+                throw new Exception("attempt to call non-function: " + f)
+              }
+            }
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+   }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+    // core.scala: defined using scala
+    core.ns.map{case (k: String,v: Any) => { repl_env.set(Symbol(k), v) }}
+    repl_env.set(Symbol("eval"), (a: List[Any]) => EVAL(a(0), repl_env))
+    repl_env.set(Symbol("*ARGV*"), args.slice(1,args.length).toList)
+
+    // core.mal: defined using the language itself
+    REP("(def! not (fn* (a) (if a false true)))")
+    REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+
+    if (args.length > 0) {
+      REP("(load-file \"" + args(0) + "\")")
+      System.exit(0)
+    }
+
+    // repl loop
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Throwable => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step8_macros.scala b/scala/step8_macros.scala
new file mode 100644 (file)
index 0000000..8e4cc56
--- /dev/null
@@ -0,0 +1,204 @@
+import env.Env
+import types.Function
+
+object step8_macros {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def is_pair(x: Any): Boolean = {
+    types._sequential_Q(x) && types._toIter(x).length > 0
+  }
+
+  def quasiquote(ast: Any): Any = {
+    if (!is_pair(ast)) {
+      return List(Symbol("quote"), ast)
+    } else {
+      val a0 = types._toList(ast)(0)
+      if (types._symbol_Q(a0) &&
+          a0.asInstanceOf[Symbol].name == "unquote") {
+        return types._toList(ast)(1)
+      } else if (is_pair(a0)) {
+        val a00 = types._toList(a0)(0)
+        if (types._symbol_Q(a00) &&
+            a00.asInstanceOf[Symbol].name == "splice-unquote") {
+          return List(Symbol("concat"),
+                      types._toList(a0)(1),
+                      quasiquote(types._toList(ast).drop(1)))
+        }
+      }
+      return List(Symbol("cons"),
+                  quasiquote(a0),
+                  quasiquote(types._toList(ast).drop(1)))
+    }
+  }
+
+  def is_macro_call(ast: Any, env: Env): Boolean = {
+    ast match {
+      case l: List[Any] => {
+        if (types._symbol_Q(l(0)) &&
+            env.find(l(0).asInstanceOf[Symbol]) != null) {
+          env.get(l(0).asInstanceOf[Symbol]) match {
+            case f: Function => return f.ismacro
+            case _ => return false
+          }
+        }
+        return false
+      }
+      case _ => return false
+    }
+  }
+
+  def macroexpand(orig_ast: Any, env: Env): Any = {
+    var ast = orig_ast;
+    while (is_macro_call(ast, env)) {
+      ast.asInstanceOf[List[Any]] match {
+        case f :: args => {
+          val mac = env.get(f.asInstanceOf[Symbol])
+          ast = mac.asInstanceOf[Function](args)
+        }
+        case _ => throw new Exception("macroexpand: invalid call")
+      }
+    }
+    ast
+  }
+
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(orig_ast: Any, orig_env: Env): Any = {
+   var ast = orig_ast; var env = orig_env;
+   while (true) {
+
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast = macroexpand(ast, env)
+    if (!ast.isInstanceOf[List[Any]]) return ast
+
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        for (g <- types._toIter(a1).grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        env = let_env
+        ast = a2   // continue loop (TCO)
+      }
+      case Symbol("quote") :: a1 :: Nil => {
+        return a1
+      }
+      case Symbol("quasiquote") :: a1 :: Nil => {
+        ast = quasiquote(a1)  // continue loop (TCO)
+      }
+      case Symbol("defmacro!") :: a1 :: a2 :: Nil => {
+        val f = EVAL(a2, env)
+        f.asInstanceOf[Function].ismacro = true
+        return env.set(a1.asInstanceOf[Symbol], f)
+      }
+      case Symbol("macroexpand") :: a1 :: Nil => {
+        return macroexpand(a1, env)
+      }
+      case Symbol("do") :: rest => {
+        eval_ast(rest.slice(0,rest.length-1), env)
+        ast = ast.asInstanceOf[List[Any]].last  // continue loop (TCO)
+      }
+      case Symbol("if") :: a1 :: a2 :: rest => {
+        val cond = EVAL(a1, env)
+        if (cond == null || cond == false) {
+          if (rest.length == 0) return null
+          ast = rest(0)  // continue loop (TCO)
+        } else {
+          ast = a2  // continue loop (TCO)
+        }
+      }
+      case Symbol("fn*") :: a1 :: a2 :: Nil => {
+        return new Function(a2, env, types._toList(a1),
+          (args: List[Any]) => {
+            EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+          }
+        )
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            f match {
+              case fn: Function => {
+                env = fn.gen_env(el) 
+                ast = fn.ast  // continue loop (TCO)
+              }
+              case fn: ((List[Any]) => Any) @unchecked => {
+                return fn(el)
+              }
+              case _ => {
+                throw new Exception("attempt to call non-function: " + f)
+              }
+            }
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+   }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+    // core.scala: defined using scala
+    core.ns.map{case (k: String,v: Any) => { repl_env.set(Symbol(k), v) }}
+    repl_env.set(Symbol("eval"), (a: List[Any]) => EVAL(a(0), repl_env))
+    repl_env.set(Symbol("*ARGV*"), args.slice(1,args.length).toList)
+
+    // core.mal: defined using the language itself
+    REP("(def! not (fn* (a) (if a false true)))")
+    REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+    REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
+    REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
+
+
+    if (args.length > 0) {
+      REP("(load-file \"" + args(0) + "\")")
+      System.exit(0)
+    }
+
+    // repl loop
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Throwable => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/step9_try.scala b/scala/step9_try.scala
new file mode 100644 (file)
index 0000000..200d3cf
--- /dev/null
@@ -0,0 +1,224 @@
+import env.Env
+import types.Function
+
+object step9_try {
+  // read
+  def READ(str: String): Any = {
+    reader.read_str(str)
+  }
+
+  // eval
+  def is_pair(x: Any): Boolean = {
+    types._sequential_Q(x) && types._toIter(x).length > 0
+  }
+
+  def quasiquote(ast: Any): Any = {
+    if (!is_pair(ast)) {
+      return List(Symbol("quote"), ast)
+    } else {
+      val a0 = types._toList(ast)(0)
+      if (types._symbol_Q(a0) &&
+          a0.asInstanceOf[Symbol].name == "unquote") {
+        return types._toList(ast)(1)
+      } else if (is_pair(a0)) {
+        val a00 = types._toList(a0)(0)
+        if (types._symbol_Q(a00) &&
+            a00.asInstanceOf[Symbol].name == "splice-unquote") {
+          return List(Symbol("concat"),
+                      types._toList(a0)(1),
+                      quasiquote(types._toList(ast).drop(1)))
+        }
+      }
+      return List(Symbol("cons"),
+                  quasiquote(a0),
+                  quasiquote(types._toList(ast).drop(1)))
+    }
+  }
+
+  def is_macro_call(ast: Any, env: Env): Boolean = {
+    ast match {
+      case l: List[Any] => {
+        if (types._symbol_Q(l(0)) &&
+            env.find(l(0).asInstanceOf[Symbol]) != null) {
+          env.get(l(0).asInstanceOf[Symbol]) match {
+            case f: Function => return f.ismacro
+            case _ => return false
+          }
+        }
+        return false
+      }
+      case _ => return false
+    }
+  }
+
+  def macroexpand(orig_ast: Any, env: Env): Any = {
+    var ast = orig_ast;
+    while (is_macro_call(ast, env)) {
+      ast.asInstanceOf[List[Any]] match {
+        case f :: args => {
+          val mac = env.get(f.asInstanceOf[Symbol])
+          ast = mac.asInstanceOf[Function](args)
+        }
+        case _ => throw new Exception("macroexpand: invalid call")
+      }
+    }
+    ast
+  }
+
+  def eval_ast(ast: Any, env: Env): Any = {
+    ast match {
+      case s : Symbol    => env.get(s)
+      case l: List[Any]  => l.map(EVAL(_, env))
+      case v: Array[Any] => v.map(EVAL(_, env)).toArray
+      case m: Map[String @unchecked,Any @unchecked] => {
+        m.map{case (k: String,v: Any) => (k, EVAL(v, env))}.toMap
+      }
+      case _             => ast
+    }
+  }
+
+  def EVAL(orig_ast: Any, orig_env: Env): Any = {
+   var ast = orig_ast; var env = orig_env;
+   while (true) {
+
+    //println("EVAL: " + printer._pr_str(ast,true))
+    if (!ast.isInstanceOf[List[Any]])
+      return eval_ast(ast, env)
+
+    // apply list
+    ast = macroexpand(ast, env)
+    if (!ast.isInstanceOf[List[Any]]) return ast
+
+    ast.asInstanceOf[List[Any]] match {
+      case Symbol("def!") :: a1 :: a2 :: Nil => {
+        return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
+      }
+      case Symbol("let*") :: a1 :: a2 :: Nil => {
+        val let_env = new Env(env)
+        for (g <- types._toIter(a1).grouped(2)) {
+          let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
+        }
+        env = let_env
+        ast = a2   // continue loop (TCO)
+      }
+      case Symbol("quote") :: a1 :: Nil => {
+        return a1
+      }
+      case Symbol("quasiquote") :: a1 :: Nil => {
+        ast = quasiquote(a1)  // continue loop (TCO)
+      }
+      case Symbol("defmacro!") :: a1 :: a2 :: Nil => {
+        val f = EVAL(a2, env)
+        f.asInstanceOf[Function].ismacro = true
+        return env.set(a1.asInstanceOf[Symbol], f)
+      }
+      case Symbol("macroexpand") :: a1 :: Nil => {
+        return macroexpand(a1, env)
+      }
+      case Symbol("try*") :: a1 :: rest => {
+        try {
+          return EVAL(a1, env)
+        } catch {
+          case t: Throwable => {
+            rest(0) match {
+              case List(Symbol("catch*"), a21, a22) => {
+                val exc: Any = t match {
+                  case mex: types.MalException => mex.value
+                  case _ => t.getMessage
+                }
+                return EVAL(a22, new Env(env,
+                                         List(a21).iterator,
+                                         List(exc).iterator))
+              }
+            }
+            throw t
+          }
+        }
+      }
+      case Symbol("do") :: rest => {
+        eval_ast(rest.slice(0,rest.length-1), env)
+        ast = ast.asInstanceOf[List[Any]].last  // continue loop (TCO)
+      }
+      case Symbol("if") :: a1 :: a2 :: rest => {
+        val cond = EVAL(a1, env)
+        if (cond == null || cond == false) {
+          if (rest.length == 0) return null
+          ast = rest(0)  // continue loop (TCO)
+        } else {
+          ast = a2  // continue loop (TCO)
+        }
+      }
+      case Symbol("fn*") :: a1 :: a2 :: Nil => {
+        return new Function(a2, env, types._toList(a1),
+          (args: List[Any]) => {
+            EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
+          }
+        )
+      }
+      case _ => {
+        // function call
+        eval_ast(ast, env) match {
+          case f :: el => {
+            f match {
+              case fn: Function => {
+                env = fn.gen_env(el) 
+                ast = fn.ast  // continue loop (TCO)
+              }
+              case fn: ((List[Any]) => Any) @unchecked => {
+                return fn(el)
+              }
+              case _ => {
+                throw new Exception("attempt to call non-function: " + f)
+              }
+            }
+          }
+          case _ => throw new Exception("invalid apply")
+        }
+      }
+    }
+   }
+  }
+
+  // print
+  def PRINT(exp: Any): String = {
+    printer._pr_str(exp, true)
+  }
+
+  // repl
+  def main(args: Array[String]) = {
+    val repl_env: Env = new Env()
+    val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
+
+    // core.scala: defined using scala
+    core.ns.map{case (k: String,v: Any) => { repl_env.set(Symbol(k), v) }}
+    repl_env.set(Symbol("eval"), (a: List[Any]) => EVAL(a(0), repl_env))
+    repl_env.set(Symbol("*ARGV*"), args.slice(1,args.length).toList)
+
+    // core.mal: defined using the language itself
+    REP("(def! not (fn* (a) (if a false true)))")
+    REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+    REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
+    REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
+
+
+    if (args.length > 0) {
+      REP("(load-file \"" + args(0) + "\")")
+      System.exit(0)
+    }
+
+    // repl loop
+    var line:String = null
+    while ({line = readLine("user> "); line != null}) {
+      try {
+        println(REP(line))
+      } catch {
+        case e : Throwable => {
+          println("Error: " + e.getMessage)
+          println("    " + e.getStackTrace.mkString("\n    "))
+        }
+      }
+    }
+  }
+}
+
+// vim: ts=2:sw=2
diff --git a/scala/types.scala b/scala/types.scala
new file mode 100644 (file)
index 0000000..cf93ac2
--- /dev/null
@@ -0,0 +1,128 @@
+import scala.collection._
+import scala.collection.generic._
+
+import env.Env
+
+object types {
+  class MalException(msg: String) extends Throwable(msg) {
+    var value: Any = null
+    def init(obj: Any) = { value = obj; this }
+  }
+
+  def _toIter(obj: Any): Iterator[Any] = {
+    obj match {
+      case l: List[Any] => l.iterator
+      case v: Array[Any] => v.iterator
+      case null => Iterator.empty
+      case _ => throw new Exception("cannot convert " +
+                                    obj.getClass + " to iterator")
+    }
+  }
+
+  def _toList(obj: Any): List[Any] = {
+    obj match {
+      case l: List[Any] => l
+      case v: Array[Any] => v.toList
+      case null => List()
+      case _ => throw new Exception("cannot convert " +
+                                    obj.getClass + " to list")
+    }
+  }
+
+  def _equal_Q(a: Any, b: Any): Any = {
+    (a, b) match {
+      case (a: List[Any], b: List[Any])   => a == b
+      case (a: Array[Any], b: Array[Any]) => a.deep == b.deep
+      case (a: List[Any], b: Array[Any])  => a == b.deep
+      case (a: Array[Any], b: List[Any])  => a.deep == b
+      case (a: Map[String @unchecked,Any @unchecked],
+            b: Map[String @unchecked,Any @unchecked]) => a == b
+      case _ => a == b
+    }
+  }
+
+  def _sequential_Q(a: Any): Boolean = {
+    a match {
+      case l: List[Any] => true
+      case v: Array[Any] => true
+      case _ => false
+    }
+  }
+
+  def _symbol_Q(a: Any) = { a.isInstanceOf[Symbol] }
+
+
+  // Lists
+
+  class MalList[A](seq : A*) extends Traversable[A]
+                             with GenericTraversableTemplate[A, MalList]
+                             with TraversableLike[A, MalList[A]] {
+    var meta: Any = null
+    override def companion = MalList
+    def foreach[U](f: A => U) = seq.foreach(f)
+  }
+  object MalList extends TraversableFactory[MalList] {
+    implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, MalList[A]] = new GenericCanBuildFrom[A]
+    def newBuilder[A] = new scala.collection.mutable.LazyBuilder[A,MalList[A]] {
+      def result = {
+        val data = parts.foldLeft(List[A]()){(l,n) => l ++ n}
+        new MalList(data:_*)
+      }
+    }
+  }
+
+
+  // Vectors
+  class MalVector[A](seq : A*) extends Traversable[A]
+                               with GenericTraversableTemplate[A, MalVector]
+                               with TraversableLike[A, MalVector[A]] {
+    var meta: Any = null
+    override def companion = MalVector
+    def foreach[U](f: A => U) = seq.foreach(f)
+  }
+  object MalVector extends TraversableFactory[MalVector] {
+    implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, MalVector[A]] = new GenericCanBuildFrom[A]
+    def newBuilder[A] = new scala.collection.mutable.LazyBuilder[A,MalVector[A]] {
+      def result = {
+        val data = parts.foldLeft(List[A]()){(l,n) => l ++ n}
+        new MalVector(data:_*)
+      }
+    }
+  }
+
+
+  class Function(_ast: Any, _env: Env, _params: List[Any],
+                 fn: ((List[Any]) => Any)) {
+    val ast = _ast
+    val env = _env
+    val params = _params
+    var ismacro = false
+
+    def apply(args: List[Any]): Any = {
+      fn(args)
+    }
+
+    def gen_env(args: List[Any]): Env = {
+      return new Env(env, params.iterator, args.iterator)
+    }
+  }
+
+  def _apply(f: Any, args: List[Any]): Any = {
+    f match {
+      case fn: types.Function => fn(args)
+      case fn: ((List[Any]) => Any) @unchecked => fn(args)
+      case _ => throw new Exception("attempt to call non-function")
+    }
+  }
+
+  def _hash_map(lst: List[Any]): Any = {
+    lst.grouped(2).map(
+      (kv: List[Any]) => (kv(0).asInstanceOf[String], kv(1))).toMap
+  }
+
+  class Atom(_value: Any) {
+    var value = _value
+  }
+}
+
+// vim:ts=2:sw=2